2015-12-01 31 views
7

Trong mã của tôi, tôi có chuẩn như:Tại sao chuỗi.HasPrefix nhanh hơn byte.HasPrefix?

const STR = "abcd" 
const PREFIX = "ab" 
var STR_B = []byte(STR) 
var PREFIX_B = []byte(PREFIX) 

func BenchmarkStrHasPrefix(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     strings.HasPrefix(STR, PREFIX) 
    } 
} 

func BenchmarkBytHasPrefix(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     bytes.HasPrefix(STR_B, PREFIX_B) 
    } 
} 

Và tôi chút chút nhầm lẫn về kết quả:

BenchmarkStrHasPrefix-4 300000000 4.67 ns/op 
BenchmarkBytHasPrefix-4 200000000 8.05 ns/op 

Tại sao có lên đến 2x sự khác biệt?

Cảm ơn.

+1

các giá trị của 'STR',' STR_B', 'PREFIX',' PREFIX_B' là gì? –

+0

@IsmailBadawi cập nhật ví dụ) – gobwas

+0

lưu ý rằng trên Core2Duo P8600 của tôi cả hai điểm chuẩn đang thực hiện tương tự (~ 20 ns/op), trong khi trên i5-2400S chúng tương tự như của bạn - 5,5 vs 8.5 ns/op – tomasz

Trả lời

13

Lý do chính là chênh lệch chi phí gọi số bytes.HasPrefix()strings.HasPrefix(). Như @tomasz đã chỉ ra trong nhận xét của mình, strings.HashPrefix() được gạch chân theo mặc định trong khi bytes.HasPrefix() thì không.

lý do tiếp theo là các loại thông số khác nhau: bytes.HasPrefix() mất 2 lát (2 mô tả lát). strings.HasPrefix() mất 2 chuỗi (2 chuỗi tiêu đề). Slice mô tả chứa một con trỏ và 2 int s: chiều dài và năng lực, xem reflect.SliceHeader. header chuỗi chỉ chứa một con trỏ và int: chiều dài, xem reflect.StringHeader.

Chúng tôi có thể chứng minh điều này nếu chúng tôi tự điền các hàm HasPrefix() vào các hàm chuẩn, vì vậy, chúng tôi loại bỏ chi phí gọi (không bằng cả hai). Bằng cách nhấn mạnh chúng, sẽ không có cuộc gọi chức năng nào được thực hiện (đối với chúng).

HasPrefix() triển khai:

// HasPrefix tests whether the byte slice s begins with prefix. 
func HasPrefix(s, prefix []byte) bool { 
    return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) 
} 

// HasPrefix tests whether the string s begins with prefix. 
func HasPrefix(s, prefix string) bool { 
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix 
} 

chức năng Benchmark sau nội tuyến họ:

func BenchmarkStrHasPrefix(b *testing.B) { 
    s, prefix := STR, PREFIX 
    for i := 0; i < b.N; i++ { 
     _ = len(s) >= len(prefix) && s[0:len(prefix)] == prefix 
    } 
} 

func BenchmarkBytHasPrefix(b *testing.B) { 
    s, prefix := STR_B, PREFIX_B 
    for i := 0; i < b.N; i++ { 
     _ = len(s) >= len(prefix) && bytes.Equal(s[0:len(prefix)], prefix) 
    } 
} 

Chạy các bạn sẽ có được kết quả rất chặt chẽ:

BenchmarkStrHasPrefix-2 300000000    5.88 ns/op 
BenchmarkBytHasPrefix-2 200000000    6.17 ns/op 

Lý do cho sự khác biệt nhỏ trong điểm chuẩn nội tuyến có thể là cả hai hàm đều kiểm tra prese nce của tiền tố bằng cách cắt các toán hạng string[]byte. Và kể từ khi string s có thể so sánh trong khi byte lát không, BenchmarkBytHasPrefix() đòi hỏi một cuộc gọi chức năng bổ sung cho bytes.Equal() so với BenchmarkStrHasPrefix() (và chức năng gọi thêm cũng bao gồm việc sao chép các thông số của nó: 2 header lát).

Những thứ khác có thể đóng góp một chút vào kết quả khác nhau ban đầu: đối số được sử dụng trong BenchmarkStrHasPrefix() là hằng số, trong khi các tham số được sử dụng trong BenchmarkBytHasPrefix() là các biến.

Bạn không nên lo lắng nhiều về sự khác biệt hiệu suất, cả hai chức năng hoàn chỉnh chỉ trong vài nano giây.

Lưu ý rằng "thực hiện" của bytes.Equal():

func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s 

Điều này có thể được inlined trong một số nền tảng dẫn đến không có chi phí cuộc gọi bổ sung.

+0

Điều đó không đủ giải thích: Nếu bạn chèn thêm một chức năng không nội tuyến, nó vẫn chậm hơn. Đáng buồn là tôi không hiểu chức năng bình đẳng trong lắp ráp thực sự là gì. Ai có thể giải thích? – 0x434D53

+1

@ 0x434D53 +1, ví dụ: trong asm_amd64.s (Dòng 1312 (thời gian chạy · eqstring) và dòng 1652 (byte = Bình đẳng)) - sự khác biệt trong mã đó là gì? – gobwas

+0

@icza cảm ơn, nhưng bạn có thể khai thác rõ ràng hơn không? =) – gobwas

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