2009-06-17 35 views
9

Tôi đang cần hàm băm nhanh nhất có thể trong Delphi 2009 sẽ tạo ra các giá trị băm từ một chuỗi Unicode sẽ phân phối khá ngẫu nhiên vào các thùng.Chức năng băm Unicode hiệu quả nhất cho Delphi 2009

tôi bắt đầu với HashOf chức năng Gabr 's từ GpStringHash:

function HashOf(const key: string): cardinal; 
asm 
    xor edx,edx  { result := 0 } 
    and eax,eax  { test if 0 } 
    jz @End   { skip if nil } 
    mov ecx,[eax-4] { ecx := string length } 
    jecxz @End  { skip if length = 0 } 
@loop:   { repeat } 
    rol edx,2  { edx := (edx shl 2) or (edx shr 30)... } 
    xor dl,[eax] { ... xor Ord(key[eax]) } 
    inc eax   { inc(eax) } 
    loop @loop  { until ecx = 0 } 
@End: 
    mov eax,edx  { result := eax } 
end; { HashOf } 

Nhưng tôi thấy rằng điều này đã không tạo ra số tốt từ chuỗi Unicode. Tôi lưu ý rằng thói quen Gabr của chưa được cập nhật để Delphi 2009.

Sau đó, tôi phát hiện ra HashNameMBCS trong SysUtils của Delphi 2009 và dịch nó với chức năng đơn giản này (nơi "chuỗi" là một Delphi 2009 Unicode string):

function HashOf(const key: string): cardinal; 
var 
    I: integer; 
begin 
    Result := 0; 
    for I := 1 to length(key) do 
    begin 
    Result := (Result shl 5) or (Result shr 27); 
    Result := Result xor Cardinal(key[I]); 
    end; 
end; { HashOf } 

tôi nghĩ đây là khá tốt cho đến khi tôi nhìn vào cửa sổ CPU và thấy mã lắp ráp nó tạo ra:

Process.pas.1649: Result := 0; 
0048DEA8 33DB    xor ebx,ebx 
Process.pas.1650: for I := 1 to length(key) do begin 
0048DEAA 8BC6    mov eax,esi 
0048DEAC E89734F7FF  call $00401348 
0048DEB1 85C0    test eax,eax 
0048DEB3 7E1C    jle $0048ded1 
0048DEB5 BA01000000  mov edx,$00000001 
Process.pas.1651: Result := (Result shl 5) or (Result shr 27); 
0048DEBA 8BCB    mov ecx,ebx 
0048DEBC C1E105   shl ecx,$05 
0048DEBF C1EB1B   shr ebx,$1b 
0048DEC2 0BCB    or ecx,ebx 
0048DEC4 8BD9    mov ebx,ecx 
Process.pas.1652: Result := Result xor Cardinal(key[I]); 
0048DEC6 0FB74C56FE  movzx ecx,[esi+edx*2-$02] 
0048DECB 33D9    xor ebx,ecx 
Process.pas.1653: end; 
0048DECD 42    inc edx 
Process.pas.1650: for I := 1 to length(key) do begin 
0048DECE 48    dec eax 
0048DECF 75E9    jnz $0048deba 
Process.pas.1654: end; { HashOf } 
0048DED1 8BC3    mov eax,ebx 

Điều này dường như chứa khá nhiều mã lắp ráp nhiều hơn so với mã Gabr của.

Tốc độ là bản chất. Có bất cứ điều gì tôi có thể làm để cải thiện hoặc mã pascal tôi đã viết hoặc lắp ráp mã của tôi tạo ra?


Theo dõi.

Cuối cùng tôi đã đi với hàm HashOf dựa trên SysUtils.HashNameMBCS. Nó dường như cung cấp cho một phân phối băm tốt cho chuỗi Unicode, và dường như khá nhanh.

Có, có rất nhiều mã trình tạo ra, nhưng mã Delphi tạo ra nó rất đơn giản và chỉ sử dụng thao tác bit-shift, vì vậy thật khó để tin rằng nó sẽ không nhanh.

+0

Trong HashOf cuối cùng của bạn, tôi nên đi từ 1 đến Độ dài (khóa). – gabr

+0

@gabr: Cảm ơn. Tôi bây giờ thấy tôi đã viết "followup" thậm chí không nhận ra rằng tôi đã kết thúc bằng cách sử dụng cùng một chức năng câu hỏi của tôi là về, ngoại trừ tôi đã thực hiện các sai lầm trong followup của tôi. Tôi sẽ viết lại điều đó. – lkessler

Trả lời

9

Đầu ra ASM không phải là dấu hiệu tốt về tốc độ thuật toán. Ngoài ra, từ những gì tôi có thể thấy, hai đoạn mã đang thực hiện hầu hết công việc giống hệt nhau. Sự khác biệt lớn nhất dường như là chiến lược truy cập bộ nhớ và đầu tiên là sử dụng roll-left thay vì tập hợp các lệnh tương đương (shl | shr - các ngôn ngữ lập trình bậc cao nhất bỏ qua các toán tử "roll"). Sau này có thể đường ống tốt hơn so với trước đây.

Tối ưu hóa ASM là ma thuật đen và đôi khi nhiều hướng dẫn thực thi nhanh hơn ít hơn.

Để chắc chắn, đo điểm chuẩn và chọn người chiến thắng. Nếu bạn thích đầu ra của giá trị thứ hai nhưng đầu tiên nhanh hơn, hãy cắm giá trị thứ hai vào đầu tiên.

rol edx,5 { edx := (edx shl 5) or (edx shr 27)... } 

Lưu ý rằng các máy khác nhau sẽ chạy mã theo cách khác nhau, sau đó đánh giá nó trên phần cứng bạn định chạy ứng dụng cuối cùng. Tôi sẵn sàng đặt cược rằng hơn megabyte dữ liệu sự khác biệt sẽ là một phần nghìn giây - ít hơn nhiều so với hệ điều hành đang lấy đi khỏi bạn.


PS. Tôi không thuyết phục thuật toán này tạo ra phân phối thậm chí, một cái gì đó bạn gọi một cách rõ ràng (có bạn chạy các biểu đồ?). Bạn có thể xem xét cổng this hash function tới Delphi.Nó có thể không nhanh như thuật toán trên nhưng nó có vẻ khá nhanh và cũng cho phép phân phối tốt. Một lần nữa, có lẽ chúng ta đang nói về thứ tự của mili giây chênh lệch trên megabyte dữ liệu.

+1

Tôi không thể đồng ý với điều này đủ. Trên các bộ vi xử lý hiện đại, cố gắng để tay tối ưu hóa lắp ráp là rất gần nếu không thực sự là một điều của quá khứ. – Lee

+0

Tôi đánh giá cao ý tưởng của bạn. Tôi không thực sự cố gắng để đi điên tối ưu hóa mã lắp ráp. Nhưng tôi muốn loại bỏ chi phí rõ ràng. Một lần chạy chương trình của tôi có thể gọi hàm băm hàng trăm triệu lần vì nó được sử dụng cho hầu hết mọi thứ – lkessler

+2

@lkessler, Không có nhiều chi phí để loại bỏ ở đây. Có thể bạn sẽ tìm thấy các tối ưu hóa lớn hơn để tìm ra các địa điểm để lưu trữ giá trị hơn là bạn sẽ ép ra một vài phần nghìn giây thực thi trong hàm băm. Khi bạn lập hồ sơ ứng dụng của bạn và thấy rằng hầu hết thời gian của bạn đang được chi tiêu trong phương pháp băm có hai tùy chọn - tối ưu hóa hàm băm (không phải là nhiều hơn nữa để đi) hoặc tìm ra cách gọi nó ít hơn. Đặt cược tốt nhất của bạn ngay bây giờ là đặt cược sau. – Talljoe

5

Chúng tôi đã tổ chức một cuộc thi nhỏ thoải mái một thời gian trở lại, cải thiện trên băm gọi là "MurmurHash"; Trích dẫn Wikipedia:

Cần lưu ý cho là nhanh hơn đặc biệt nhanh, thường 2-4 lần hơn các thuật toán so sánh như FNV, lookup3 Jenkins' và Hsieh SuperFastHash, với tuyệt vời phân phối, hành vi sạt lở và kháng va chạm tổng thể.

Bạn có thể tải xuống nội dung gửi cho cuộc thi đó here.

Một điều chúng tôi đã học được là, đôi khi việc tối ưu hóa không cải thiện kết quả trên mỗi CPU. Đóng góp của tôi đã được tinh chỉnh để chạy tốt trên AMD, nhưng thực hiện không tốt trên Intel. Một cách khác xung quanh cũng xảy ra (Intel tối ưu hóa chạy phụ tối ưu trên AMD).

Vì vậy, như Talljoe đã nói: hãy đo lường các tối ưu hóa của bạn, vì chúng thực sự có thể gây bất lợi cho hiệu suất của bạn!

Lưu ý: Tôi không đồng ý với Lee; Delphi là một trình biên dịch tốt đẹp và tất cả, nhưng đôi khi tôi thấy nó tạo ra mã mà không phải là tối ưu (ngay cả khi biên dịch với tất cả các tối ưu hóa bật). Ví dụ, tôi thường xuyên thấy nó xóa thanh ghi mà đã được xóa chỉ hai hoặc ba báo cáo trước đây. Hoặc EAX được đưa vào EBX, chỉ có nó được chuyển và đưa trở lại vào EAX. Đó là loại điều. Tôi chỉ đoán ở đây, nhưng việc tối ưu hóa bằng tay loại mã đó chắc chắn sẽ giúp đỡ ở những chỗ chật hẹp.

Phía trên tất cả; Trước tiên hãy phân tích nút cổ chai của bạn, sau đó xem liệu thuật toán hay datastructure tốt hơn có thể được sử dụng hay không, sau đó thử tối ưu hóa mã pascal (như: giảm phân bổ bộ nhớ, tránh đếm tham chiếu, hoàn thành, thử/cuối cùng, thử/trừ các khối, v.v.) và sau đó, chỉ là một khu nghỉ mát cuối cùng, tối ưu hóa mã lắp ráp.

5

Tôi đã viết hai hàm "tối ưu hóa" được lắp đặt trong Delphi, hoặc thực hiện nhiều thuật toán băm nhanh hơn được biết đến trong cả pascal tinh chỉnh và Borland Assembler. Đầu tiên là triển khai thực hiện SuperFastHash và thứ hai là việc thực hiện MurmurHash2 được kích hoạt bởi một yêu cầu từ Tommi Prami trên blog của tôi để dịch phiên bản C# của tôi sang triển khai thực hiện pascal. Điều này đã sinh ra một số discussion continued on the Embarcadero Discussion BASM Forums, cuối cùng dẫn đến khoảng 20 lần triển khai (kiểm tra latest benchmark suite) và cuối cùng cho thấy khó chọn được triển khai tốt nhất do sự khác biệt lớn về thời gian chu kỳ trên mỗi lệnh giữa Intel và AMD.

Vì vậy, hãy thử một trong số đó, nhưng hãy nhớ, nhận được nhanh nhất mỗi lần có thể có nghĩa là thay đổi thuật toán thành một thuật toán đơn giản hơn sẽ làm tổn hại đến phân phối của bạn. Tinh chỉnh một quá trình triển khai mất rất nhiều thời gian và tốt hơn nên tạo một bộ kiểm tra và đánh giá tốt để kiểm tra việc triển khai của bạn.

+0

Davy: Thật tuyệt khi được nghe từ người đã thực hiện công việc. Tôi đã lưu ý việc thực hiện của bạn trong nhận xét của tôi về câu trả lời của talljoe, và cuộc thảo luận đã được chỉ ra bởi PhiS. Dường như SuperFastHash có rất nhiều mã, đặc biệt là khi bạn so sánh nó với sáu dòng pascal trong hàm HashOf của câu hỏi của tôi. Tôi tự hỏi điều gì sẽ làm cho SuperFastHash nhanh hơn HashOf, và nếu nó nhanh hơn, thì bao nhiêu? – lkessler

+0

@lkessler: các câu hỏi của bạn đều chỉ ra những gì đã được đề cập trong mọi câu trả lời, tạo chương trình điểm chuẩn để mô phỏng việc sử dụng hàm băm, đo tốc độ và phân phối của bạn và bạn có thể tìm ra lý do tại sao SuperFastHash/MurmurHash2 có thể chậm hơn HashOf. Đối với chuỗi nhỏ (10 ký tự) tôi sẽ * mong đợi * HashOf được nhanh hơn, cho các chuỗi lớn hơn các chức năng khác có vòng chưa được kiểm soát để tận dụng lợi thế. –

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