2015-05-29 19 views
15

Câu hỏi này là về tốc độ truy cập các thành phần của mảng và lát, không phải về hiệu quả truyền chúng tới các hàm làm đối số.Mảng vs Slice: tốc độ truy cập

Tôi mong chờ mảng là nhanh hơn so với lát trong nhiều trường hợp vì một lát là một cấu trúc dữ liệu mô tả một phần tiếp giáp của một mảng và do đó có thể có thêm một bước tham gia khi truy cập vào các yếu tố của một lát (gián tiếp các phần tử của mảng cơ bản của nó).

Vì vậy, tôi đã viết một bài kiểm tra nhỏ để chuẩn một loạt các hoạt động đơn giản. Có 4 chức năng chuẩn, 2 thử nghiệm đầu tiên một toàn cầu lát và một mảng toàn cầu, 2 thử nghiệm khác một địa phương lát và một mảng địa phương:

var gs = make([]byte, 1000) // Global slice 
var ga [1000]byte   // Global array 

func BenchmarkSliceGlobal(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     for j, v := range gs { 
      gs[j]++; gs[j] = gs[j] + v + 10; gs[j] += v 
     } 
    } 
} 

func BenchmarkArrayGlobal(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     for j, v := range ga { 
      ga[j]++; ga[j] = ga[j] + v + 10; ga[j] += v 
     } 
    } 
} 

func BenchmarkSliceLocal(b *testing.B) { 
    var s = make([]byte, 1000) 
    for i := 0; i < b.N; i++ { 
     for j, v := range s { 
      s[j]++; s[j] = s[j] + v + 10; s[j] += v 
     } 
    } 
} 

func BenchmarkArrayLocal(b *testing.B) { 
    var a [1000]byte 
    for i := 0; i < b.N; i++ { 
     for j, v := range a { 
      a[j]++; a[j] = a[j] + v + 10; a[j] += v 
     } 
    } 
} 

Tôi chạy thử nghiệm nhiều lần, đây là sản lượng tiêu biểu (go test -bench .*):

BenchmarkSliceGlobal  300000    4210 ns/op 
BenchmarkArrayGlobal  300000    4123 ns/op 
BenchmarkSliceLocal  500000    3090 ns/op 
BenchmarkArrayLocal  500000    3768 ns/op 

Phân tích kết quả:

Truy cập lát toàn cầu là hơi chậm hơn so với truy cập vào mảng toàn cầu mà là như tôi mong đợi:
4210 vs 4123 ns/op

Nhưng truy cập vào lát địa phương là đáng kể nhanh hơn so với truy cập vào các mảng địa phương:
3090 vs 3768 ns/op

Câu hỏi của tôi là: Lý do cho điều này là gì?

Ghi chú

tôi đã cố gắng thay đổi những điều sau đây nhưng không ai thay đổi kết quả:

  • kích thước của mảng/lát (thử 100, 1000, 10000)
  • thứ tự của các hàm chuẩn
  • loại phần tử của mảng/lát (đã thử byteint)
+0

Có một số yếu tố ảnh hưởng đến các tiêu chí vi mô như vậy: Kiểm tra giới hạn, phân bổ (đống, ngăn xếp), tạo mã, tối ưu hóa lổ nhìn trộm, v.v. Hãy xem kết quả lắp ráp được tạo ra trong các điều kiện khác nhau như kiểm tra bị vô hiệu hóa hoặc tắt tối ưu hóa. – Volker

+1

Điều thú vị là, nếu bạn thêm một [con trỏ vào mảng] (http://play.golang.org/p/nyynQgndDl) vào điểm chuẩn, bạn sẽ thấy rằng chúng hoạt động giống như các lát cắt. –

Trả lời

11

So sánh the amd64 assembly của cả hai BenchmarkArrayLocalBenchmarkSliceLocal (quá dài để phù hợp trong bài viết này):

Phiên bản mảng nạp địa chỉ của a từ bộ nhớ nhiều lần, thực tế trên tất cả các hoạt động mảng truy cập:

LEAQ "".a+1000(SP),BX 

trong khi phiên bản lát được tính toán hoàn toàn vào thanh ghi sau khi tải một lần từ bộ nhớ:

LEAQ (DX)(SI*1),BX 

Đây không phải là kết luận nhưng có thể là nguyên nhân. Lý do là cả hai phương pháp là nếu không hầu như giống hệt nhau.Một trong những chi tiết đáng chú ý khác là phiên bản mảng gọi vào runtime.duffcopy, đó là một thói quen lắp ráp khá dài, trong khi phiên bản slice thì không.

+0

Nhưng 'runtime.duffcopy()' chỉ được gọi một lần phải không? Thấy rằng chu trình chuẩn được thực hiện nửa triệu lần ('N'), điều này sẽ không ảnh hưởng đến kết quả. – icza

+0

Vâng, đó là những gì tôi nghĩ là tốt. – thwd

+0

Liệu kết quả gần như giống nhau trong trường hợp mảng và mảng toàn cục xuất phát từ thực tế là trong trường hợp đó mã được biên dịch không bao gồm tối ưu hóa "registry" và thực hiện cùng tải địa chỉ từ bộ nhớ? – icza

0

Tới phiên bản 1.8 có thể loại bỏ một số kiểm tra phạm vi để chênh lệch lớn hơn.

BenchmarkSliceGlobal-4 500000 3220 ns/op BenchmarkArrayGlobal-4 1000000 1287 ns/op BenchmarkSliceLocal-4 1000000 1267 ns/op BenchmarkArrayLocal-4 1000000 1301 ns/op

Đối với mảng tôi khuyên bạn nên sử dụng các kích cỡ từ quyền hạn của hai và bao gồm một hợp lý và hoạt động. Bằng cách đó bạn chắc chắn trình biên dịch loại bỏ việc kiểm tra. Do đó var ga [1024]byte với ga[j & 1023].

Các vấn đề liên quan