Nhanh chóng tìm xem một giá trị có trong một mảng C không?


124

Tôi có một ứng dụng nhúng với ISR ​​quan trọng về thời gian cần lặp qua một mảng có kích thước 256 (tốt nhất là 1024, nhưng 256 là mức tối thiểu) và kiểm tra xem giá trị có khớp với nội dung của mảng không. A boolsẽ được đặt thành đúng là trường hợp này.

Bộ vi điều khiển là lõi NXP LPC4357, lõi ARM Cortex M4 và trình biên dịch là GCC. Tôi đã kết hợp tối ưu hóa mức 2 (3 chậm hơn) và đặt chức năng vào RAM thay vì flash. Tôi cũng sử dụng số học con trỏ và một forvòng lặp, đếm ngược thay vì lên (kiểm tra xem i!=0có nhanh hơn kiểm tra nếu i<256). Nói chung, tôi kết thúc với thời lượng 12,5, số lượng phải giảm đáng kể để có thể khả thi. Đây là mã (giả) tôi sử dụng bây giờ:

uint32_t i;
uint32_t *array_ptr = &theArray[0];
uint32_t compareVal = 0x1234ABCD;
bool validFlag = false;

for (i=256; i!=0; i--)
{
    if (compareVal == *array_ptr++)
    {
         validFlag = true;
         break;
     }
}

Điều gì sẽ là cách nhanh nhất tuyệt đối để làm điều này? Sử dụng lắp ráp nội tuyến được cho phép. Các thủ thuật 'kém thanh lịch' khác cũng được cho phép.


28
Có cách nào để lưu trữ giá trị trong mảng khác nhau không? Nếu bạn có thể sắp xếp chúng, tìm kiếm nhị phân chắc chắn sẽ nhanh hơn. Nếu dữ liệu được lưu trữ và tìm kiếm nằm trong một phạm vi nhất định, chúng có thể được biểu thị bằng một bản đồ bit, v.v.
Remo.D

20
@BitBank: bạn sẽ ngạc nhiên về số lượng trình biên dịch đã được cải thiện trong ba thập kỷ qua. ARM đặc biệt là khá thân thiện với trình biên dịch. Và tôi biết một thực tế rằng ARM trên GCC có thể ban hành các hướng dẫn tải nhiều lần (ít nhất là từ năm 2009)
MSalters

8
Câu hỏi tuyệt vời, mọi người quên có những trường hợp thực tế trong đó vấn đề hiệu suất. quá nhiều lần những câu hỏi như thế này được trả lời bằng "chỉ sử dụng stl"
Kik

14
Tiêu đề "... lặp qua một mảng" là sai lệch vì thực sự bạn chỉ đang tìm kiếm một giá trị nhất định. Lặp đi lặp lại trên một mảng ngụ ý một cái gì đó được thực hiện trên mỗi mục. Sắp xếp, nếu chi phí có thể được khấu hao theo nhiều tìm kiếm, thực sự là một cách tiếp cận hiệu quả độc lập với các vấn đề triển khai ngôn ngữ.
hardmath

8
Bạn có chắc chắn rằng bạn không thể đơn giản sử dụng tìm kiếm nhị phân hoặc bảng băm không? Tìm kiếm nhị phân cho 256 mục == 8 so sánh. Một bảng băm == 1 lần nhảy trung bình (hoặc 1 lần nhảy tối đa nếu bạn có một hàm băm hoàn hảo). Bạn chỉ nên sử dụng tối ưu hóa lắp ráp sau khi bạn 1) có thuật toán tìm kiếm phù hợp ( O(1)hoặc O(logN), so với O(N)) và 2) bạn đã mô tả nó là nút cổ chai.
Groo

Câu trả lời:


105

Trong các tình huống mà hiệu suất là vô cùng quan trọng, trình biên dịch C rất có thể sẽ không tạo ra mã nhanh nhất so với những gì bạn có thể làm với ngôn ngữ lắp ráp được điều chỉnh bằng tay. Tôi có xu hướng đi theo con đường ít kháng cự nhất - đối với các thói quen nhỏ như thế này, tôi chỉ viết mã asm và có một ý tưởng tốt là sẽ mất bao nhiêu chu kỳ để thực hiện. Bạn có thể sử dụng mã C và có được trình biên dịch để tạo đầu ra tốt, nhưng cuối cùng bạn có thể lãng phí rất nhiều thời gian để điều chỉnh đầu ra theo cách đó. Trình biên dịch (đặc biệt là từ Microsoft) đã đi một chặng đường dài trong vài năm qua, nhưng chúng vẫn không thông minh bằng trình biên dịch giữa hai tai của bạn vì bạn đang làm việc trong tình huống cụ thể của mình chứ không chỉ là trường hợp chung. Trình biên dịch có thể không sử dụng các hướng dẫn nhất định (ví dụ LDM) có thể tăng tốc độ này và nó ' s không đủ thông minh để bỏ qua vòng lặp. Đây là một cách để làm điều đó kết hợp 3 ý tưởng mà tôi đã đề cập trong nhận xét của mình: Hủy bỏ vòng lặp, tìm nạp trước bộ đệm và sử dụng hướng dẫn nhiều tải (ldm). Số lượng chu kỳ hướng dẫn xuất hiện khoảng 3 đồng hồ trên mỗi phần tử mảng, nhưng điều này không tính đến độ trễ bộ nhớ.

Lý thuyết vận hành: Thiết kế CPU của ARM thực hiện hầu hết các lệnh trong một chu kỳ xung nhịp, nhưng các hướng dẫn được thực thi trong một đường ống. Trình biên dịch C sẽ cố gắng loại bỏ sự chậm trễ đường ống bằng cách xen kẽ các hướng dẫn khác ở giữa. Khi được trình bày với một vòng lặp chặt chẽ như mã C ban đầu, trình biên dịch sẽ khó có thể che giấu sự chậm trễ vì giá trị đọc từ bộ nhớ phải được so sánh ngay lập tức. Mã của tôi dưới đây xen kẽ giữa 2 bộ 4 thanh ghi để giảm đáng kể độ trễ của bộ nhớ và đường ống tìm nạp dữ liệu. Nói chung, khi làm việc với các tập dữ liệu lớn và mã của bạn không sử dụng hầu hết hoặc tất cả các thanh ghi có sẵn, thì bạn sẽ không đạt được hiệu suất tối đa.

; r0 = count, r1 = source ptr, r2 = comparison value

   stmfd sp!,{r4-r11}   ; save non-volatile registers
   mov r3,r0,LSR #3     ; loop count = total count / 8
   pld [r1,#128]
   ldmia r1!,{r4-r7}    ; pre load first set
loop_top:
   pld [r1,#128]
   ldmia r1!,{r8-r11}   ; pre load second set
   cmp r4,r2            ; search for match
   cmpne r5,r2          ; use conditional execution to avoid extra branch instructions
   cmpne r6,r2
   cmpne r7,r2
   beq found_it
   ldmia r1!,{r4-r7}    ; use 2 sets of registers to hide load delays
   cmp r8,r2
   cmpne r9,r2
   cmpne r10,r2
   cmpne r11,r2
   beq found_it
   subs r3,r3,#1        ; decrement loop count
   bne loop_top
   mov r0,#0            ; return value = false (not found)
   ldmia sp!,{r4-r11}   ; restore non-volatile registers
   bx lr                ; return
found_it:
   mov r0,#1            ; return true
   ldmia sp!,{r4-r11}
   bx lr

Cập nhật: Có rất nhiều người hoài nghi trong các ý kiến ​​cho rằng kinh nghiệm của tôi là giai thoại / vô giá trị và yêu cầu bằng chứng. Tôi đã sử dụng GCC 4.8 (từ Android NDK 9C) để tạo đầu ra sau với tối ưu hóa -O2 (tất cả các tối ưu hóa được bật bao gồm cả không kiểm soát vòng lặp ). Tôi đã biên dịch mã C ban đầu được trình bày trong câu hỏi trên. Đây là những gì GCC sản xuất:

.L9: cmp r3, r0
     beq .L8
.L3: ldr r2, [r3, #4]!
     cmp r2, r1
     bne .L9
     mov r0, #1
.L2: add sp, sp, #1024
     bx  lr
.L8: mov r0, #0
     b .L2

Đầu ra của GCC không chỉ không kiểm soát vòng lặp mà còn lãng phí đồng hồ trên một gian hàng sau LDR. Nó đòi hỏi ít nhất 8 đồng hồ cho mỗi phần tử mảng. Nó thực hiện tốt việc sử dụng địa chỉ để biết khi nào thoát khỏi vòng lặp, nhưng tất cả các trình biên dịch những điều kỳ diệu có khả năng thực hiện đều không tìm thấy trong mã này. Tôi chưa chạy mã trên nền tảng đích (tôi không sở hữu mã này), nhưng bất kỳ ai có kinh nghiệm về hiệu suất mã ARM đều có thể thấy mã của tôi nhanh hơn.

Cập nhật 2: Tôi đã cho Microsoft Visual Studio 2013 SP2 cơ hội để làm tốt hơn với mã. Nó có thể sử dụng các hướng dẫn NEON để vectơ khởi tạo mảng của tôi, nhưng tìm kiếm giá trị tuyến tính được viết bởi OP xuất hiện tương tự như những gì GCC đã tạo (tôi đổi tên các nhãn để dễ đọc hơn):

loop_top:
   ldr  r3,[r1],#4  
   cmp  r3,r2  
   beq  true_exit
   subs r0,r0,#1 
   bne  loop_top
false_exit: xxx
   bx   lr
true_exit: xxx
   bx   lr

Như tôi đã nói, tôi không sở hữu phần cứng chính xác của OP, nhưng tôi sẽ thử nghiệm hiệu năng trên nVidia Tegra 3 và Tegra 4 của 3 phiên bản khác nhau và sớm đăng kết quả tại đây.

Cập nhật 3: Tôi đã chạy mã của tôi và mã ARM được biên dịch của Microsoft trên Tegra 3 và Tegra 4 (Surface RT, Surface RT 2). Tôi đã chạy 1000000 lần lặp của một vòng lặp mà không tìm thấy kết quả khớp để mọi thứ đều nằm trong bộ đệm và thật dễ dàng để đo.

             My Code       MS Code
Surface RT    297ns         562ns
Surface RT 2  172ns         296ns  

Trong cả hai trường hợp, mã của tôi chạy nhanh gần gấp đôi. Hầu hết các CPU ARM hiện đại có thể sẽ cho kết quả tương tự.


13
@ LưuViênPhúc - nói chung là đúng, nhưng ISR chặt chẽ là một trong những trường hợp ngoại lệ lớn nhất, ở chỗ bạn thường biết nhiều hơn trình biên dịch.
sapi

47
Người ủng hộ của quỷ: có bằng chứng định lượng nào cho thấy mã này nhanh hơn không?
Oliver Charlesworth

11
@BitBank: Điều đó không đủ tốt. Bạn phải sao lưu các yêu cầu của mình với bằng chứng .
Các cuộc đua nhẹ nhàng trong quỹ đạo

13
Tôi đã học được bài học của tôi nhiều năm trước. Tôi đã tạo ra một vòng lặp bên trong được tối ưu hóa tuyệt vời cho một thói quen đồ họa trên Pentium, sử dụng tối ưu các ống U và V. Có được nó xuống đến 6 chu kỳ đồng hồ trên mỗi vòng lặp (tính toán và đo lường), và tôi rất tự hào về bản thân mình. Khi tôi kiểm tra nó chống lại điều tương tự được viết bằng C, C nhanh hơn. Tôi không bao giờ viết một dòng lắp ráp Intel nữa.
Rocketmagnet

14
"những người hoài nghi trong các ý kiến ​​cho rằng kinh nghiệm của tôi là giai thoại / vô giá trị và cần bằng chứng." Đừng lấy ý kiến ​​của họ quá tiêu cực. Hiển thị bằng chứng chỉ làm cho câu trả lời tuyệt vời của bạn tốt hơn nhiều.
Cody Grey

87

Có một mẹo để tối ưu hóa nó (tôi đã được hỏi điều này trong một cuộc phỏng vấn xin việc một lần):

  • Nếu mục cuối cùng trong mảng giữ giá trị mà bạn đang tìm kiếm, thì trả về true
  • Viết giá trị mà bạn đang tìm kiếm vào mục cuối cùng trong mảng
  • Lặp lại mảng cho đến khi bạn gặp giá trị mà bạn đang tìm kiếm
  • Nếu bạn đã gặp nó trước mục cuối cùng trong mảng, thì trả về true
  • Trả lại sai

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t i;
    uint32_t x = theArray[SIZE-1];
    if (x == compareVal)
        return true;
    theArray[SIZE-1] = compareVal;
    for (i = 0; theArray[i] != compareVal; i++);
    theArray[SIZE-1] = x;
    return i != SIZE-1;
}

Điều này mang lại một nhánh cho mỗi lần lặp thay vì hai nhánh mỗi lần lặp.


CẬP NHẬT:

Nếu bạn được phép phân bổ mảng cho SIZE+1, thì bạn có thể thoát khỏi phần "hoán đổi mục nhập cuối cùng":

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t i;
    theArray[SIZE] = compareVal;
    for (i = 0; theArray[i] != compareVal; i++);
    return i != SIZE;
}

Bạn cũng có thể thoát khỏi số học bổ sung được nhúng vào theArray[i], sử dụng cách sau:

bool check(uint32_t theArray[], uint32_t compareVal)
{
    uint32_t *arrayPtr;
    theArray[SIZE] = compareVal;
    for (arrayPtr = theArray; *arrayPtr != compareVal; arrayPtr++);
    return arrayPtr != theArray+SIZE;
}

Nếu trình biên dịch không áp dụng nó, thì chức năng này chắc chắn sẽ làm như vậy. Mặt khác, nó có thể khiến trình tối ưu hóa khó kiểm soát vòng lặp hơn, do đó bạn sẽ phải xác minh rằng trong mã lắp ráp được tạo ...


2
@ratchetfreak: OP không cung cấp bất kỳ chi tiết nào về cách thức, vị trí và thời điểm mảng này được phân bổ và khởi tạo, vì vậy tôi đã đưa ra một câu trả lời không phụ thuộc vào điều đó.
barak manos

3
Mảng là trong RAM, viết không được phép mặc dù.
wlamers

1
tốt đẹp, nhưng mảng không còn nữa const, điều này làm cho điều này không an toàn cho chuỗi. Có vẻ như một mức giá cao phải trả.
EOF

2
@EOF: Trường hợp đã consttừng được đề cập trong câu hỏi?
barak manos

4
@barakmanos: Nếu tôi chuyển một mảng và một giá trị cho bạn và hỏi bạn xem giá trị đó có trong mảng không, tôi thường không cho rằng bạn sẽ sửa đổi mảng. Câu hỏi ban đầu đề cập đến cũng không constphải chủ đề, nhưng tôi nghĩ thật công bằng khi đề cập đến sự cảnh báo này.
EOF

62

Bạn đang yêu cầu trợ giúp với việc tối ưu hóa thuật toán của bạn, điều này có thể đẩy bạn đến trình biên dịch chương trình. Nhưng thuật toán của bạn (một tìm kiếm tuyến tính) không quá thông minh, vì vậy bạn nên xem xét thay đổi thuật toán của mình. Ví dụ:

Hàm băm hoàn hảo

Nếu 256 giá trị "hợp lệ" của bạn là tĩnh và được biết đến tại thời điểm biên dịch, thì bạn có thể sử dụng hàm băm hoàn hảo . Bạn cần tìm một hàm băm ánh xạ giá trị đầu vào của bạn thành một giá trị trong phạm vi 0 .. n , trong đó không có xung đột cho tất cả các giá trị hợp lệ mà bạn quan tâm. Đó là, không có hai giá trị "hợp lệ" băm cho cùng một giá trị đầu ra. Khi tìm kiếm một hàm băm tốt, bạn nhắm đến:

  • Giữ chức năng băm nhanh chóng hợp lý.
  • Giảm thiểu n . Nhỏ nhất bạn có thể nhận được là 256 (hàm băm hoàn hảo tối thiểu), nhưng điều đó có thể khó đạt được, tùy thuộc vào dữ liệu.

Lưu ý đối với các hàm băm hiệu quả, n thường có công suất bằng 2, tương đương với mặt nạ bit bit của các bit thấp (hoạt động AND). Hàm băm ví dụ:

  • CRC của byte đầu vào, modulo n .
  • ((x << i) ^ (x >> j) ^ (x << k) ^ ...) % n(chọn càng nhiều i, j, k, ... khi cần thiết, với những thay đổi trái hoặc phải)

Sau đó, bạn tạo một bảng cố định gồm n mục, trong đó hàm băm ánh xạ các giá trị đầu vào thành một chỉ mục i vào bảng. Đối với các giá trị hợp lệ, mục nhập bảng i chứa giá trị hợp lệ. Đối với tất cả các mục trong bảng khác, đảm bảo rằng mỗi mục nhập của chỉ mục i chứa một số giá trị không hợp lệ khác không băm vào i .

Sau đó, trong thói quen ngắt của bạn, với đầu vào x :

  1. Hash x đến chỉ mục i (nằm trong phạm vi 0..n)
  2. Tra cứu mục i trong bảng và xem nó có chứa giá trị x không .

Điều này sẽ nhanh hơn nhiều so với tìm kiếm tuyến tính gồm 256 hoặc 1024 giá trị.

Tôi đã viết một số mã Python để tìm các hàm băm hợp lý.

Tìm kiếm nhị phân

Nếu bạn sắp xếp mảng 256 giá trị "hợp lệ" của mình, thì bạn có thể thực hiện tìm kiếm nhị phân , thay vì tìm kiếm tuyến tính. Điều đó có nghĩa là bạn sẽ có thể tìm kiếm bảng 256 mục chỉ trong 8 bước ( log2(256)) hoặc bảng 1024 mục trong 10 bước. Một lần nữa, điều này sẽ nhanh hơn nhiều so với tìm kiếm tuyến tính gồm 256 hoặc 1024 giá trị.


Cảm ơn vì điều đó. Tùy chọn tìm kiếm nhị phân là tùy chọn tôi đã chọn. Xem thêm một bình luận trước đó trong bài viết đầu tiên. Điều này không lừa rất tốt mà không cần sử dụng lắp ráp.
lang thang

11
Thật vậy, trước khi cố gắng tối ưu hóa mã của bạn (chẳng hạn như sử dụng lắp ráp hoặc các thủ thuật khác), bạn có thể nên xem liệu bạn có thể giảm độ phức tạp thuật toán hay không. Thông thường việc giảm độ phức tạp thuật toán sẽ hiệu quả hơn so với cố gắng tạo ra một vài chu kỳ nhưng vẫn giữ nguyên độ phức tạp thuật toán.
ysdx

3
+1 cho tìm kiếm nhị phân. Thiết kế lại thuật toán là cách tốt nhất để tối ưu hóa.
Rocketmagnet

Một quan niệm phổ biến là cần quá nhiều nỗ lực để tìm một thói quen băm hiệu quả, do đó, "thực tiễn tốt nhất" là tìm kiếm nhị phân. Đôi khi, "thực hành tốt nhất" là không đủ tốt. Giả sử bạn đang định tuyến lưu lượng truy cập mạng ngay lúc có tiêu đề của gói tin (nhưng không phải là trọng tải của nó): sử dụng tìm kiếm nhị phân sẽ khiến sản phẩm của bạn bị chậm một cách vô vọng. Các sản phẩm nhúng thường có các ràng buộc và yêu cầu như vậy, ví dụ như "thực tiễn tốt nhất" trong môi trường thực thi x86 là "lấy ra một cách dễ dàng" trong nhúng.
Olof Forshell

60

Giữ bảng theo thứ tự được sắp xếp và sử dụng tìm kiếm nhị phân không được kiểm soát của Bentley:

i = 0;
if (key >= a[i+512]) i += 512;
if (key >= a[i+256]) i += 256;
if (key >= a[i+128]) i += 128;
if (key >= a[i+ 64]) i +=  64;
if (key >= a[i+ 32]) i +=  32;
if (key >= a[i+ 16]) i +=  16;
if (key >= a[i+  8]) i +=   8;
if (key >= a[i+  4]) i +=   4;
if (key >= a[i+  2]) i +=   2;
if (key >= a[i+  1]) i +=   1;
return (key == a[i]);

Điểm mấu chốt là,

  • nếu bạn biết cái bàn lớn như thế nào, thì bạn sẽ biết có bao nhiêu lần lặp lại, vì vậy bạn hoàn toàn có thể hủy đăng ký nó.
  • Sau đó, không có kiểm tra điểm nào cho ==trường hợp trên mỗi lần lặp bởi vì, ngoại trừ lần lặp cuối cùng, xác suất của trường hợp đó là quá thấp để biện minh cho việc dành thời gian kiểm tra cho nó. **
  • Cuối cùng, bằng cách mở rộng bảng thành lũy thừa 2, bạn thêm tối đa một so sánh và tối đa là một yếu tố của hai bộ lưu trữ.

** Nếu bạn không quen suy nghĩ về xác suất, mọi điểm quyết định đều có một entropy , đó là thông tin trung bình bạn học được bằng cách thực hiện nó. Đối với các >=bài kiểm tra, xác suất của mỗi nhánh là khoảng 0,5 và -log2 (0,5) là 1, vì vậy điều đó có nghĩa là nếu bạn lấy một nhánh bạn học được 1 bit, và nếu bạn lấy nhánh khác bạn học một bit và trung bình chỉ là tổng của những gì bạn học được trên mỗi nhánh nhân với xác suất của nhánh đó. Vì vậy 1*0.5 + 1*0.5 = 1, entropy của>= bài kiểm tra là 1. Vì bạn có 10 bit để học, nên cần 10 nhánh. Đó là lý do tại sao nó nhanh!

Mặt khác, nếu thử nghiệm đầu tiên của bạn là if (key == a[i+512)gì? Xác suất đúng là 1/1024, trong khi xác suất sai là 1023/1024. Vì vậy, nếu đó là sự thật, bạn học được tất cả 10 bit! Nhưng nếu đó là sai, bạn học -log2 (1023/1024) = .00141 bit, thực tế không có gì! Vì vậy, số tiền trung bình bạn học được từ bài kiểm tra đó là 10/1024 + .00141*1023/1024 = .0098 + .00141 = .0112bit. Khoảng một phần trăm của một chút. Bài kiểm tra đó không mang trọng lượng của nó!


4
Tôi thực sự thích giải pháp này. Nó có thể được sửa đổi để chạy trong một số chu kỳ cố định để tránh pháp y dựa trên thời gian nếu vị trí của giá trị là thông tin nhạy cảm.
OregonTrail

1
@OrebTrail: Pháp y dựa trên thời gian? Vấn đề vui, nhưng bình luận buồn.
Mike Dunlavey

16
Bạn thấy các vòng lặp không được kiểm soát như thế này trong các thư viện tiền điện tử để ngăn chặn các cuộc tấn công thời gian en.wikipedia.org/wiki/Timing_attack . Đây là một ví dụ tốt github.com/jedisct1/libsodium/blob/ nam Trong trường hợp này, chúng tôi đang ngăn kẻ tấn công đoán độ dài của chuỗi. Thông thường, kẻ tấn công sẽ lấy vài triệu mẫu của một lời gọi hàm để thực hiện một cuộc tấn công thời gian.
OregonTrail

3
+1 Tuyệt vời! Đẹp ít tìm kiếm không kiểm soát. Tôi đã không nhìn thấy điều đó trước đây. Tôi có thể sử dụng nó.
Rocketmagnet

1
@OrebTrail: Tôi thứ hai nhận xét dựa trên thời gian của bạn. Tôi đã hơn một lần phải viết mã mật mã thực thi trong một số chu kỳ cố định, để tránh rò rỉ thông tin cho các cuộc tấn công dựa trên thời gian.
TonyK

16

Nếu tập hợp các hằng số trong bảng của bạn được biết trước, bạn có thể sử dụng băm hoàn hảo để đảm bảo rằng chỉ có một quyền truy cập được thực hiện vào bảng. Băm hoàn hảo xác định hàm băm ánh xạ mọi khóa thú vị vào một vị trí duy nhất (bảng đó không phải lúc nào cũng dày đặc, nhưng bạn có thể quyết định mức độ dày đặc của một bảng bạn có thể chi trả, với các bảng ít đậm đặc hơn thường dẫn đến các hàm băm đơn giản hơn).

Thông thường, hàm băm hoàn hảo cho bộ khóa cụ thể tương đối dễ tính toán; bạn không muốn điều đó kéo dài và phức tạp bởi vì việc cạnh tranh về thời gian có lẽ tốt hơn dành cho việc thực hiện nhiều thăm dò.

Băm hoàn hảo là sơ đồ "1 đầu dò tối đa". Người ta có thể khái quát hóa ý tưởng, với suy nghĩ rằng người ta nên đánh đổi sự đơn giản của việc tính toán mã băm với thời gian cần thiết để thực hiện k thăm dò. Xét cho cùng, mục tiêu là "tổng thời gian tìm kiếm ít nhất", không phải là ít đầu dò hoặc hàm băm đơn giản nhất. Tuy nhiên, tôi chưa bao giờ thấy ai xây dựng thuật toán băm k-probes-max. Tôi nghi ngờ một người có thể làm điều đó, nhưng đó có thể là nghiên cứu.

Một suy nghĩ khác: nếu bộ xử lý của bạn cực kỳ nhanh, thì một đầu dò vào bộ nhớ từ hàm băm hoàn hảo có thể chi phối thời gian thực hiện. Nếu bộ xử lý không nhanh lắm, thì k> 1 đầu dò có thể là thực tế.


1
Một chiếc Cortex-M không ở đâu gần cực kỳ nhanh .
MSalters

2
Trong thực tế, trong trường hợp này, anh ta không cần bất kỳ bảng băm nào cả. Anh ta chỉ muốn biết nếu một khóa nhất định có trong tập hợp, anh ta không muốn ánh xạ nó tới một giá trị. Vậy là đủ nếu hàm băm hoàn hảo ánh xạ mỗi giá trị 32 bit thành 0 hoặc 1 trong đó "1" có thể được định nghĩa là "nằm trong tập hợp".
David Ongaro

1
Điểm tốt, nếu anh ta có thể có được một trình tạo hàm băm hoàn hảo để tạo ra một ánh xạ như vậy. Nhưng, đó sẽ là "một tập hợp cực kỳ dày đặc"; Tôi có thể tìm thấy một trình tạo hàm băm hoàn hảo để thực hiện điều đó. Anh ta có thể tốt hơn khi cố gắng có được một hàm băm hoàn hảo tạo ra một số K không đổi nếu trong tập hợp và bất kỳ giá trị nào trừ K nếu không có trong tập hợp. Tôi nghi ngờ rằng thật khó để có được một hàm băm hoàn hảo ngay cả đối với cái sau.
Ira Baxter

@DavidOngaro table[PerfectHash(value)] == valuemang lại 1 nếu giá trị nằm trong tập hợp và 0 nếu không, và có nhiều cách nổi tiếng để tạo hàm PerfectHash (xem, ví dụ: burtleburtle.net/bob/hash/perinf.html ). Cố gắng tìm một hàm băm ánh xạ trực tiếp tất cả các giá trị trong tập thành 1 và tất cả các giá trị không nằm trong tập thành 0 là một nhiệm vụ khó khăn.
Jim Balter

@DavidOngaro: một hàm băm hoàn hảo có nhiều "dương tính giả", nghĩa là các giá trị không có trong tập hợp sẽ có cùng hàm băm như các giá trị trong tập hợp. Vì vậy, bạn phải có một bảng, được lập chỉ mục bởi giá trị băm, chứa giá trị đầu vào "trong tập hợp". Vì vậy, để xác nhận bất kỳ giá trị đầu vào nào, bạn (a) băm nó; (b) sử dụng giá trị băm để thực hiện tra cứu bảng; (c) kiểm tra xem mục trong bảng có khớp với giá trị đầu vào không.
Craig McQueen

14

Sử dụng một bộ băm. Nó sẽ cho O (1) thời gian tra cứu.

Đoạn mã sau giả định rằng bạn có thể dự trữ giá trị 0dưới dạng giá trị 'trống', nghĩa là không xảy ra trong dữ liệu thực tế. Giải pháp có thể được mở rộng cho một tình huống trong đó không phải là trường hợp này.

#define HASH(x) (((x >> 16) ^ x) & 1023)
#define HASH_LEN 1024
uint32_t my_hash[HASH_LEN];

int lookup(uint32_t value)
{
    int i = HASH(value);
    while (my_hash[i] != 0 && my_hash[i] != value) i = (i + 1) % HASH_LEN;
    return i;
}

void store(uint32_t value)
{
    int i = lookup(value);
    if (my_hash[i] == 0)
       my_hash[i] = value;
}

bool contains(uint32_t value)
{
    return (my_hash[lookup(value)] == value);
}

Trong ví dụ triển khai này, thời gian tra cứu thường sẽ rất thấp, nhưng trong trường hợp xấu nhất có thể lên đến số lượng mục được lưu trữ. Đối với một ứng dụng thời gian thực, bạn cũng có thể xem xét việc triển khai bằng cách sử dụng cây nhị phân, sẽ có thời gian tra cứu dễ dự đoán hơn.


3
Nó phụ thuộc vào số lần tra cứu này phải được thực hiện để có hiệu quả.
maxywb

1
Er, tra cứu có thể chạy ra cuối mảng. Và kiểu băm tuyến tính này có tỷ lệ va chạm cao - không có cách nào bạn sẽ nhận được O (1). Bộ băm tốt không được thực hiện như thế này.
Jim Balter

@JimBalter Đúng, mã không hoàn hảo. Giống như ý tưởng chung hơn; có thể chỉ trỏ đến mã bộ băm hiện có. Nhưng xem xét rằng đây là một thói quen dịch vụ gián đoạn, có thể hữu ích để chứng minh rằng việc tra cứu không phải là mã rất phức tạp.
JPA

Bạn chỉ nên sửa nó để nó quấn tôi xung quanh.
Jim Balter

Điểm của một hàm băm hoàn hảo là nó thực hiện một thăm dò. Giai đoạn = Stage.
Ira Baxter

10

Trong trường hợp này, có thể đáng để điều tra các bộ lọc Bloom . Họ có khả năng nhanh chóng thiết lập rằng không có giá trị, đó là một điều tốt vì hầu hết 2 ^ 32 giá trị có thể không nằm trong mảng phần tử 1024 đó. Tuy nhiên, có một số dương tính giả sẽ cần kiểm tra thêm.

Vì bảng của bạn rõ ràng là tĩnh, bạn có thể xác định những tích cực sai nào tồn tại cho bộ lọc Bloom của bạn và đặt chúng vào một hàm băm hoàn hảo.


1
Thật thú vị, tôi đã không thấy bộ lọc Bloom trước đây.
Rocketmagnet

8

Giả sử bộ xử lý của bạn chạy ở tốc độ 204 MHz dường như là mức tối đa cho LPC4357 và cũng giả sử kết quả thời gian của bạn phản ánh trường hợp trung bình (một nửa số mảng đi qua), chúng tôi nhận được:

  • Tần số CPU: 204 MHz
  • Chu kỳ: 4,9 ns
  • Thời lượng tính theo chu kỳ: 12,5 Nhận / 4,9 ns = 2551 chu kỳ
  • Chu kỳ mỗi lần lặp: 2551/128 = 19,9

Vì vậy, vòng tìm kiếm của bạn dành khoảng 20 chu kỳ cho mỗi lần lặp. Điều đó không có vẻ khủng khiếp, nhưng tôi đoán rằng để làm cho nó nhanh hơn, bạn cần phải nhìn vào lắp ráp.

Tôi sẽ khuyên bạn nên bỏ chỉ mục và sử dụng một so sánh con trỏ thay vào đó, và thực hiện tất cả các con trỏ const.

bool arrayContains(const uint32_t *array, size_t length)
{
  const uint32_t * const end = array + length;
  while(array != end)
  {
    if(*array++ == 0x1234ABCD)
      return true;
  }
  return false;
}

Đó ít nhất là giá trị thử nghiệm.


1
-1, ARM có chế độ địa chỉ được lập chỉ mục nên điều này là vô nghĩa. Đối với việc tạo con trỏ const, GCC đã phát hiện ra rằng nó không thay đổi. Không constthêm bất cứ điều gì.
MSalters

11
@MSalters OK, tôi không xác minh bằng mã được tạo, điểm đã thể hiện một cái gì đó mà làm cho nó đơn giản hơn ở cấp C, và tôi nghĩ rằng chỉ quản lý con trỏ thay vì một con trỏ và chỉ số đơn giản hơn. Tôi chỉ đơn giản là không đồng ý rằng " constkhông thêm bất cứ thứ gì": nó nói rõ với người đọc rằng giá trị sẽ không thay đổi. Đó là thông tin tuyệt vời.
thư giãn

9
Đây là mã nhúng sâu; tối ưu hóa cho đến nay đã bao gồm việc chuyển mã từ flash sang RAM. Nhưng nó vẫn cần phải nhanh hơn. Tại thời điểm này, khả năng đọc không phải là mục tiêu.
MSalters

1
@MSalters "ARM có chế độ địa chỉ được lập chỉ mục nên điều này là vô nghĩa" - tốt, nếu bạn hoàn toàn bỏ lỡ điểm ... OP đã viết "Tôi cũng sử dụng số học con trỏ và vòng lặp for". bung ra không thay thế việc lập chỉ mục bằng con trỏ, anh ta chỉ loại bỏ biến chỉ số và do đó trừ thêm vào mỗi lần lặp. Nhưng OP rất khôn ngoan (không giống như nhiều người trả lời và bình luận) và cuối cùng đã thực hiện tìm kiếm nhị phân.
Jim Balter

6

Những người khác đã đề xuất sắp xếp lại bảng của bạn, thêm giá trị sentinel ở cuối hoặc sắp xếp nó để cung cấp tìm kiếm nhị phân.

Bạn nêu rõ "Tôi cũng sử dụng số học con trỏ và một vòng lặp for, đếm ngược thay vì lên (kiểm tra xem i != 0có nhanh hơn kiểm tra xemi < 256 )".

Lời khuyên đầu tiên của tôi là: loại bỏ số học con trỏ và đếm ngược. Những thứ như

for (i=0; i<256; i++)
{
    if (compareVal == the_array[i])
    {
       [...]
    }
}

có xu hướng là thành ngữ cho trình biên dịch. Vòng lặp là thành ngữ và việc lập chỉ mục của một mảng trên một biến vòng lặp là thành ngữ. Việc tung hứng với số học con trỏ và con trỏ sẽ có xu hướng làm xáo trộn các thành ngữ cho trình biên dịch và làm cho nó tạo mã liên quan đến những gì bạn đã viết thay vì những gì người viết trình biên dịch quyết định là khóa học tốt nhất cho tác vụ chung .

Ví dụ, đoạn mã trên có thể được biên dịch thành một vòng lặp chạy từ -256hoặc -255về 0, lập chỉ mục &the_array[256]. Có thể những thứ thậm chí không thể biểu thị bằng C hợp lệ nhưng phù hợp với kiến ​​trúc của máy bạn đang tạo.

Vì vậy, đừng vi mô hóa. Bạn chỉ cần ném các spanners vào công việc của trình tối ưu hóa của bạn. Nếu bạn muốn khéo léo, hãy làm việc trên các cấu trúc dữ liệu và thuật toán nhưng không tối đa hóa biểu thức của chúng. Nó sẽ quay lại cắn bạn, nếu không phải trên trình biên dịch / kiến ​​trúc hiện tại, thì tiếp theo.

Cụ thể là sử dụng số học con trỏ thay vì mảng và chỉ mục là độc hại cho trình biên dịch nhận thức đầy đủ về sự sắp xếp, vị trí lưu trữ, cân nhắc bí danh và các công cụ khác và để thực hiện tối ưu hóa như giảm sức mạnh theo cách phù hợp nhất với kiến ​​trúc máy.


Vòng lặp trên con trỏ là thành ngữ trong C và trình biên dịch tối ưu hóa tốt có thể xử lý chúng cũng như lập chỉ mục. Nhưng toàn bộ điều này là tranh luận vì cuối cùng OP đã thực hiện tìm kiếm nhị phân.
Jim Balter

3

Vectorization có thể được sử dụng ở đây, vì nó thường được thực hiện trong memchr. Bạn sử dụng thuật toán sau:

  1. Tạo mặt nạ lặp lại truy vấn của bạn, có độ dài bằng với số bit của hệ điều hành của bạn (64 bit, 32 bit, v.v.). Trên hệ thống 64 bit, bạn sẽ lặp lại truy vấn 32 bit hai lần.

  2. Xử lý danh sách dưới dạng danh sách nhiều mẩu dữ liệu cùng một lúc, chỉ bằng cách chuyển danh sách thành danh sách loại dữ liệu lớn hơn và kéo các giá trị ra. Đối với mỗi đoạn, XOR nó với mặt nạ, sau đó XOR với 0b0111 ... 1, sau đó thêm 1, sau đó & với mặt nạ 0b1000 ... 0 lặp lại. Nếu kết quả là 0, chắc chắn không có trận đấu. Mặt khác, có thể (thường có xác suất rất cao) là một trận đấu, vì vậy hãy tìm kiếm đoạn thông thường.

Ví dụ triển khai: https://sourceware.org/cgi-bin/cvsweb.cgi/src/newlib/libc/opes/memchr.c?rev=1.3&content-type=text/x-cvsweb-markup&cvsroot=src


3

Nếu bạn có thể chứa miền của các giá trị của mình với dung lượng bộ nhớ có sẵn cho ứng dụng của bạn, thì giải pháp nhanh nhất sẽ là biểu diễn mảng của bạn dưới dạng một mảng bit:

bool theArray[MAX_VALUE]; // of which 1024 values are true, the rest false
uint32_t compareVal = 0x1234ABCD;
bool validFlag = theArray[compareVal];

BIÊN TẬP

Tôi sửng sốt về số lượng các nhà phê bình. Tiêu đề của chủ đề này là "Làm thế nào để tôi nhanh chóng tìm thấy liệu một giá trị có trong mảng C không?" mà tôi sẽ đứng trước câu trả lời của mình vì nó trả lời chính xác điều đó. Tôi có thể lập luận rằng điều này có hàm băm hiệu quả nhất về tốc độ (vì giá trị address ===). Tôi đã đọc các bình luận và tôi nhận thức được sự cẩn thận rõ ràng. Không còn nghi ngờ gì nữa, hãy cẩn thận giới hạn phạm vi các vấn đề có thể được sử dụng để giải quyết, nhưng, đối với những vấn đề mà nó giải quyết được, nó giải quyết rất hiệu quả.

Thay vì từ chối câu trả lời này hoàn toàn, hãy coi nó là điểm khởi đầu tối ưu mà bạn có thể phát triển bằng cách sử dụng các hàm băm để đạt được sự cân bằng tốt hơn giữa tốc độ và hiệu suất.


8
Làm thế nào để có được 4 upvote? Câu hỏi cho biết đó là Cortex M4. Thứ này có RAM 136 KB, không phải là 262.144 KB.
MSalters

1
Thật đáng kinh ngạc khi có bao nhiêu upvote đã được đưa ra cho câu trả lời rõ ràng sai bởi vì người trả lời đã bỏ lỡ khu rừng cho cây. Đối với trường hợp lớn nhất của OP O (log n) << O (n).
msw

3
Tôi rất khó chịu với các lập trình viên, những người đốt cháy bộ nhớ vô lý, khi có nhiều giải pháp tốt hơn. Cứ sau 5 năm, dường như PC của tôi hết bộ nhớ, trong đó 5 năm trước số tiền đó rất nhiều.
Craig McQueen

1
@CraigMcQueen Trẻ em những ngày này. Lãng phí ký ức. Tàn nhẫn! Quay trở lại thời của tôi, chúng tôi có 1 MiB bộ nhớ và kích thước từ 16 bit. / s
Cole Johnson

2
Những gì với các nhà phê bình khắc nghiệt? OP nêu rõ tốc độ là cực kỳ quan trọng đối với phần mã này và StephenQuan đã đề cập đến một "bộ nhớ vô lý".
Bogdan Alexandru

1

Đảm bảo rằng các hướng dẫn ("mã giả") và dữ liệu ("theArray") nằm trong các bộ nhớ (RAM) riêng biệt để kiến ​​trúc CM4 Harvard được sử dụng hết tiềm năng. Từ hướng dẫn sử dụng:

nhập mô tả hình ảnh ở đây

Để tối ưu hóa hiệu suất CPU, ARM Cortex-M4 có ba xe buýt để truy cập Hướng dẫn (mã) (I), truy cập Dữ liệu (D) và truy cập Hệ thống (S). Khi các hướng dẫn và dữ liệu được lưu giữ trong các bộ nhớ riêng biệt, thì việc truy cập mã và dữ liệu có thể được thực hiện song song trong một chu kỳ. Khi mã và dữ liệu được giữ trong cùng một bộ nhớ, thì các hướng dẫn tải hoặc lưu trữ dữ liệu có thể mất hai chu kỳ.


Thật thú vị, Cortex-M7 có bộ nhớ dữ liệu / hướng dẫn tùy chọn, nhưng trước đó chắc chắn là không. vi.wikipedia.org/wiki/ARM_Cortex-M#Silicon_customization .
Peter Cordes

0

Tôi xin lỗi nếu câu trả lời của tôi đã được trả lời - chỉ là tôi là một người đọc lười biếng. Cảm thấy bạn thoải mái để downvote sau đó))

1) bạn hoàn toàn có thể xóa bộ đếm 'i' - chỉ cần so sánh các con trỏ, tức là

for (ptr = &the_array[0]; ptr < the_array+1024; ptr++)
{
    if (compareVal == *ptr)
    {
       break;
    }
}
... compare ptr and the_array+1024 here - you do not need validFlag at all.

tất cả những gì sẽ không cung cấp bất kỳ cải thiện đáng kể nào, tối ưu hóa như vậy có thể đạt được bởi chính trình biên dịch.

2) Như đã được đề cập bởi các câu trả lời khác, gần như tất cả các CPU hiện đại đều dựa trên RISC, ví dụ ARM. Ngay cả CPU Intel X86 hiện đại cũng sử dụng lõi RISC bên trong, theo như tôi biết (biên dịch từ X86 khi đang bay). Tối ưu hóa chính cho RISC là tối ưu hóa đường ống (và cho cả Intel và CPU khác), giảm thiểu nhảy mã. Một loại tối ưu hóa như vậy (có thể là một loại chính), là loại "quay vòng theo chu kỳ". Thật vô cùng ngu ngốc và hiệu quả, ngay cả trình biên dịch Intel cũng có thể làm điều đó AFAIK. Nó có vẻ như:

if (compareVal == the_array[0]) { validFlag = true; goto end_of_compare; }
if (compareVal == the_array[1]) { validFlag = true; goto end_of_compare; }
...and so on...
end_of_compare:

Bằng cách này, tối ưu hóa là đường ống không bị phá vỡ trong trường hợp xấu nhất (nếu so sánh không có trong mảng), vì vậy nó càng nhanh càng tốt (tất nhiên không tính tối ưu hóa thuật toán như bảng băm, mảng được sắp xếp, v.v. được đề cập trong các câu trả lời khác, có thể cho kết quả tốt hơn tùy thuộc vào kích thước mảng. Cách tiếp cận Cycl Rollback cũng có thể được áp dụng ở đó. Tôi viết ở đây về điều đó tôi nghĩ rằng tôi không thấy ở người khác)

Phần thứ hai của tối ưu hóa này là mục mảng được lấy theo địa chỉ trực tiếp (được tính ở giai đoạn biên dịch, đảm bảo bạn sử dụng mảng tĩnh) và không cần thêm ADD op để tính con trỏ từ địa chỉ cơ sở của mảng. Tối ưu hóa này có thể không có hiệu quả đáng kể, vì kiến ​​trúc AFAIK ARM có các tính năng đặc biệt để tăng tốc độ đánh địa chỉ mảng. Nhưng dù sao thì vẫn tốt hơn để biết rằng bạn đã làm tất cả những gì tốt nhất chỉ bằng mã C trực tiếp, phải không?

Chu kỳ Rollback có thể trông lúng túng do lãng phí ROM (vâng, bạn đã đặt đúng vào phần RAM nhanh, nếu bo mạch của bạn hỗ trợ tính năng này), nhưng thực sự đó là một khoản tiền công bằng cho tốc độ, dựa trên khái niệm RISC. Đây chỉ là một điểm chung của tối ưu hóa tính toán - bạn hy sinh không gian vì tốc độ và ngược lại, tùy thuộc vào yêu cầu của bạn.

Nếu bạn nghĩ rằng rollback cho mảng 1024 phần tử là quá lớn cho trường hợp của bạn, bạn có thể xem xét 'rollback một phần', ví dụ như chia mảng thành 2 phần của 512 mục mỗi phần, hoặc 4x256, v.v.

3) CPU hiện đại thường hỗ trợ ops SIMD, ví dụ tập lệnh ARM NEON - nó cho phép thực thi song song các op tương tự. Thẳng thắn mà nói tôi không nhớ nó có phù hợp để so sánh ops không, nhưng tôi cảm thấy có thể, bạn nên kiểm tra xem. Googling cho thấy rằng cũng có thể có một số thủ thuật, để có được tốc độ tối đa, hãy xem https://stackoverflow.com/a/5734019/1028256

Tôi hy vọng nó có thể cung cấp cho bạn một số ý tưởng mới.


OP đã bỏ qua tất cả các câu trả lời ngu ngốc tập trung vào việc tối ưu hóa các vòng lặp tuyến tính, và thay vào đó đã chọn trước mảng và thực hiện tìm kiếm nhị phân.
Jim Balter

@Jim, rõ ràng là loại tối ưu hóa đó nên được thực hiện đầu tiên. Câu trả lời 'Foolish' có thể trông không quá ngu ngốc trong một số trường hợp sử dụng khi ví dụ bạn không có thời gian để sắp xếp mảng. Hoặc nếu tốc độ bạn nhận được, dù sao thì cũng không đủ
Mixaz

"Rõ ràng là loại tối ưu hóa đó nên được thực hiện trước tiên" - rõ ràng không dành cho những người đã nỗ lực hết sức để phát triển các giải pháp tuyến tính. "Bạn không có thời gian để sắp xếp mảng" - Tôi không biết điều đó có nghĩa là gì. "Hoặc nếu tốc độ bạn nhận được, dù sao cũng không đủ" - Uh, nếu tốc độ từ tìm kiếm nhị phân là "không đủ", thực hiện tìm kiếm tuyến tính tối ưu sẽ không cải thiện nó. Bây giờ tôi đã thực hiện với chủ đề này.
Jim Balter

@JimBalter, nếu tôi gặp vấn đề như OP, tôi chắc chắn sẽ xem xét sử dụng các thuật toán như tìm kiếm nhị phân hoặc một cái gì đó. Tôi chỉ không thể nghĩ rằng OP đã không xem xét nó. "Bạn không có thời gian để sắp xếp mảng" có nghĩa là việc sắp xếp mảng cần có thời gian. Nếu bạn cần làm điều đó cho mỗi bộ dữ liệu đầu vào, có thể mất nhiều thời gian hơn một vòng lặp tuyến tính. "Hoặc nếu tốc độ bạn nhận được, dù sao cũng không đủ" nghĩa là theo sau - gợi ý tối ưu hóa ở trên có thể được sử dụng để tăng tốc mã tìm kiếm nhị phân hoặc bất cứ điều gì
Mixaz

0

Tôi là một fan hâm mộ lớn của băm. Tất nhiên, vấn đề là tìm ra một thuật toán hiệu quả, vừa nhanh, vừa sử dụng một lượng bộ nhớ tối thiểu (đặc biệt là trên bộ xử lý nhúng).

Nếu bạn biết trước các giá trị có thể xảy ra, bạn có thể tạo một chương trình chạy qua vô số thuật toán để tìm ra giá trị tốt nhất - hay nói đúng hơn là các tham số tốt nhất cho dữ liệu của bạn.

Tôi đã tạo ra một chương trình như vậy mà bạn có thể đọc trong bài này và đạt được một số kết quả rất nhanh. 16000 mục dịch khoảng 2 ^ 14 hoặc trung bình 14 so sánh để tìm giá trị bằng cách sử dụng tìm kiếm nhị phân. Tôi rõ ràng nhắm đến việc tra cứu rất nhanh - trung bình tìm thấy giá trị trong <= 1,5 lần tra cứu - dẫn đến yêu cầu RAM lớn hơn. Tôi tin rằng với giá trị trung bình bảo thủ hơn (giả sử <= 3), rất nhiều bộ nhớ có thể được lưu. Bằng cách so sánh trường hợp trung bình cho tìm kiếm nhị phân trên 256 hoặc 1024 mục của bạn sẽ dẫn đến số lượng so sánh trung bình lần lượt là 8 và 10.

Tra cứu trung bình của tôi cần khoảng 60 chu kỳ (trên máy tính xách tay có intel i5) với thuật toán chung (sử dụng một phép chia cho một biến) và 40-45 chu kỳ với một chuyên ngành (có thể sử dụng phép nhân). Điều này sẽ chuyển thành thời gian tra cứu dưới micro giây trên MCU của bạn, tất nhiên tùy thuộc vào tần số đồng hồ mà nó thực hiện tại.

Nó có thể được điều chỉnh ngoài đời thực hơn nữa nếu mảng nhập theo dõi số lần truy cập được truy cập. Nếu mảng mục nhập được sắp xếp từ hầu hết đến ít truy cập nhất trước khi phân được tính toán thì nó sẽ tìm thấy các giá trị thường xảy ra nhất với một so sánh duy nhất.


0

Điều này giống như một phụ lục hơn là một câu trả lời.

Tôi đã có một trường hợp tương tự trong quá khứ, nhưng mảng của tôi không đổi trong một số lượng tìm kiếm đáng kể.

Trong một nửa trong số đó, giá trị tìm kiếm KHÔNG có trong mảng. Sau đó, tôi nhận ra rằng tôi có thể áp dụng một "bộ lọc" trước khi thực hiện bất kỳ tìm kiếm nào.

"Bộ lọc" này chỉ là một số nguyên đơn giản, được tính ONCE và được sử dụng trong mỗi tìm kiếm.

Nó ở trong Java, nhưng nó khá đơn giản:

binaryfilter = 0;
for (int i = 0; i < array.length; i++)
{
    // just apply "Binary OR Operator" over values.
    binaryfilter = binaryfilter | array[i];
}

Vì vậy, trước khi thực hiện tìm kiếm nhị phân, tôi kiểm tra binaryfilter:

// Check binaryfilter vs value with a "Binary AND Operator"
if ((binaryfilter & valuetosearch) != valuetosearch)
{
    // valuetosearch is not in the array!
    return false;
}
else
{
    // valuetosearch MAYBE in the array, so let's check it out
    // ... do binary search stuff ...

}

Bạn có thể sử dụng thuật toán băm 'tốt hơn', nhưng điều này có thể rất nhanh, đặc biệt đối với số lượng lớn. Có thể điều này có thể giúp bạn tiết kiệm nhiều chu kỳ hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.