programing

얼른 값이 C배열에 존재한다?

goodcopy 2022. 8. 11. 23:55
반응형

얼른 값이 C배열에 존재한다?

time-critical 피(256 ( 1024 256 ) 、 A 。bool에 조정은 이러한 경우를 설정할 것이다.

Cortex 나는 이미 램 대신 플래시에 기능을 배치하는 것 최적화 수준 2(3 느리다)다.와 포인터 합니다.for아닌 )i!=0는, 「」의 유무를 보다 빠릅니다.i<256 이 필요합니다전부다, 나는 과감하게 실행할으로 삭감되어야 했다 12.5µs의 기간을 감소시키게 됩니다.나는지의의의의의의의의의의의의의의의의의 。

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;
     }
}

그 절대적인 빠른 길 이것을 해야 하나?를 사용하여 인라인 어셈블리가 허용된다.다른'less elegant의 재주 또한 허용되고 있다.

성취의 소지가 발생한 상황에서는 C컴파일러 대부분 그런 손을 맞추었다 어셈블리 언어와 함께 무엇 할 수 있는 것 비해 빠른 코드를 생성하지 않을 것이다.나는- 이렇게 작은 순서와 방법은 저항의 길을 가기, 저는 단지 그것을 실행할 것이다 좋은 생각 얼마나 많은 주기를 가지asm 코드를 쓰는 경향이 있다.당신은 C코드와 규칙과 컴파일러 좋은 출력을 생성할 내용을 조작하다, 하지만 너는 그런 방법은 출력 동조 많은 시간을 낭비하고 끝날 수 있을지 모를 수 있다.컴파일러(특히 Microsoft)는 최근 몇 년 동안 많은 발전을 이루었지만, 일반적인 케이스가 아닌 특정 상황에 대처하고 있기 때문에 컴파일러는 아직 컴파일러만큼 스마트하지 않습니다.컴파일러는 이 속도를 높일 수 있는 특정 명령(LDM 등)을 사용하지 않을 수 있으며, 루프를 풀 수 있을 만큼 스마트하지 않을 수 있습니다.다음은 제가 코멘트에서 언급한 3가지 아이디어(루프 언롤링, 캐시 프리페치, 다중 로드(ldm) 명령 사용)를 통합하는 방법입니다.명령 사이클 카운트는 어레이 요소당 약 3클럭으로 나타나지만 메모리 지연은 고려하지 않습니다.

조작 이론:ARM의 CPU 설계는 대부분의 명령어를 1클럭 사이클로 실행하지만 명령어는 파이프라인으로 실행됩니다.C 컴파일러는 그 사이에 다른 명령어를 인터리브함으로써 파이프라인 지연을 배제하려고 합니다.원래의 C 코드와 같은 엄격한 루프가 있는 경우, 메모리에서 읽어낸 값을 즉시 비교해야 하기 때문에 컴파일러는 지연을 숨기기가 어렵습니다.아래 코드는 메모리 자체와 데이터를 가져오는 파이프라인의 지연을 크게 줄이기 위해 4개의 레지스터 중 2세트를 번갈아 사용하고 있습니다.일반적으로 대용량 데이터 세트를 사용할 때 사용 가능한 레지스터의 대부분 또는 전부를 사용하지 않는 경우 최고의 성능을 얻을 수 없습니다.

; 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

갱신: 코멘트에는 제 경험이 일화/가치없고 증거가 필요하다고 생각하는 회의론자들이 많이 있습니다.GCC 4.8 (Android NDK 9C에서)를 사용하여 Optimization - O2 (루프 언롤을 포함한 모든 최적화가 켜짐)를 사용하여 다음 출력을 생성했습니다.위 질문에서 제시한 오리지널 C 코드를 정리했습니다.GCC의 성과는 다음과 같습니다.

.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

GCC의 출력은 루프를 풀지 않을 뿐만 아니라 LDR 후에 스톨로 클럭을 낭비합니다.어레이 요소당 최소 8개의 클럭이 필요합니다.주소를 사용하여 루프를 종료하는 타이밍을 알 수 있지만 컴파일러가 할 수 있는 모든 마법적인 작업은 이 코드에서는 찾을 수 없습니다.대상 플랫폼에서 코드를 실행한 적은 없지만(소유하지 않음) ARM 코드 성능에 대한 경험이 있는 사람이라면 내 코드가 더 빠르다는 것을 알 수 있습니다.

업데이트 2: Microsoft의 Visual Studio 2013 SP2에 코드를 더 잘 적용할 수 있는 기회를 제공했습니다.NEON 명령을 사용하여 어레이 초기화를 벡터화할 수 있었지만 OP에 의해 작성된 선형 값 검색은 GCC가 생성한 것과 비슷했습니다(더 읽기 쉽도록 라벨 이름을 변경했습니다).

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

말씀드렸듯이 OP의 정확한 하드웨어는 가지고 있지 않지만, 3가지 버전 중 nVidia Tegra 3과 Tegra 4에서 성능을 테스트하고 결과를 곧 여기에 게시할 예정입니다.

업데이트 3: Tegra 3 및 Tegra 4(Surface RT, Surface RT 2)에서 코드와 Microsoft의 컴파일된 ARM 코드를 실행했습니다.모든 것이 캐시에 저장되어 측정이 용이하도록 일치하는 루프를 1000000회 반복했습니다.

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

두 경우 모두 코드가 거의 2배 빠르게 실행됩니다.대부분의 최신 ARM CPU에서도 비슷한 결과가 나올 수 있습니다.

테이블을 정렬된 순서대로 유지하고 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]);

요점은,

  • 테이블의 크기를 알면 반복 횟수를 알 수 있으므로 완전히 펼 수 있습니다.
  • 이 시험은 가 안 ==마지막 반복을 제외하고, 그 경우의 확률이 너무 낮아서 각 반복에 대한 테스트에 소요되는 시간을 정당화할 수 없기 때문이다.
  • 마지막으로 표를 2의 거듭제곱으로 확장하면 최대 1개의 비교에서 최대 2개의 스토리지 계수를 더합니다.

** 확률에 대해 생각하는 것에 익숙하지 않다면, 모든 의사결정 지점에는 엔트로피가 있습니다. 엔트로피는 실행함으로써 습득하는 평균 정보입니다.를 위해>=테스트에서는 각 분기의 확률은 약 0.5이고 -log2(0.5)는 1입니다.즉, 한쪽 분기의 경우 1비트를 학습하고 다른 한쪽 분기의 경우 1비트를 학습합니다.평균은 각 분기의 학습 내용과 해당 분기의 확률을 곱한 합계입니다. ★★★★★★★★★★★★★★★★★.1*0.5 + 1*0.5 = 1, ,, 의 >=1번으로 하다.배울 게 10비트니까 10개의 브런치가 필요해요.★★★★★★★★★★★★★★★★★★★★★!

첫 이 면면 is on, is is is is is is is is is is is is is is is is is is is is is is 라면?if (key == a[i+512)은 1은 1023/1024입니다 1023 / 1023 / 10월 23일10시 10분명히 10살!-log2(1023/bit)= .00141 입니다. 사실상 아무것도 학습하지 않습니다.그래서 그 테스트에서 배우는 평균적인 양은10/1024 + .00141*1023/1024 = .0098 + .00141 = .0112100분의 1 정도요 시험은 역할을 못 해!

경우 Bloom 필터를 조사할 가치가 있습니다.이들은 값이 존재하지 않음을 빠르게 확인할 수 있습니다. 이는 가능한 2^32의 대부분의 값이 1024개의 요소 배열에 포함되지 않기 때문에 좋은 일입니다.그러나 추가 검사가 필요한 잘못된 긍정도 있습니다.

테이블은 정적이기 때문에 Bloom 필터에 대해 존재하는 false positive를 판별하여 완벽한 해시에 넣을 수 있습니다.

최적화에는 요령이 있습니다(한 번 면접에서 이런 질문을 받은 적이 있습니다).

  • 어레이의 마지막 엔트리에 원하는 값이 있으면 true를 반환합니다.
  • 어레이의 마지막 엔트리에 원하는 값을 기입합니다.
  • 원하는 값을 찾을 때까지 어레이를 반복합니다.
  • 어레이의 마지막 엔트리가 발생하기 전에 이 엔트리가 발생한 경우 true를 반환합니다.
  • Return false

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;
}

이렇게 하면 반복당 2개의 분기가 아닌 반복당 1개의 분기가 생성됩니다.


갱신:

를 에 할 수 SIZE+1 '의 스왑 수 있습니다.

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

'산수'에 를 없앨 도 있습니다.theArray[i]츠키다

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

컴파일러가 아직 적용하지 않은 경우 이 함수는 적용됩니다.한편, 옵티마이저가 루프를 풀기가 어려워질 수 있기 때문에 생성된 어셈블리 코드에서 다음 사항을 확인해야 합니다.

알고리즘을 최적화하기 위해 도움을 요청하고 있으며, 이로 인해 어셈블러로 전환될 수 있습니다.그러나 알고리즘(선형 검색)은 그다지 영리하지 않으므로 알고리즘을 변경하는 것을 고려해야 합니다.예:

완전 해시 함수

256개의 "유효한" 값이 정적이고 컴파일 시 알려진 경우 완벽한 해시 함수를 사용할 수 있습니다.입력값을 0..n 범위의 값에 매핑하는 해시함수를 찾아야 합니다.해시함수에서는 모든 유효한 값에 대해 충돌이 발생하지 않습니다.즉, 2개의 "유효한" 값이 동일한 출력 값으로 해시되지 않습니다.적절한 해시 함수를 검색할 때는 다음을 목표로 합니다.

  • 해시함수는 합리적으로 고속으로 유지합니다.
  • 최소 n.최소값은 256(최소완전해시함수)이지만 데이터에 따라서는 달성하기 어려울 수 있습니다.

효율적인 해시 함수의 경우 n은 종종 2의 거듭제곱이며, 이는 낮은 비트의 비트 단위 마스크(AND 연산)와 동일합니다.해시 함수의 예:

  • 입력 바이트의 CRC(모듈론).
  • ((x << i) ^ (x >> j) ^ (x << k) ^ ...) % n만큼)i,j,k, 오른쪽시프트를 합니다.)필요에 따라 왼쪽 또는 오른쪽 시프트 포함)

그런 다음 n개의 엔트리로 구성된 고정 테이블을 만듭니다.여기서 해시는 입력값을 인덱스 i에 매핑합니다.유효한 값의 경우 테이블 항목 i에는 유효한 값이 포함됩니다.다른 모든 테이블 항목의 경우 인덱스 i의 각 항목에 i로 해시되지 않는 다른 잘못된 값이 포함되어 있는지 확인하십시오.

다음으로 인터럽트 루틴에서 입력 x:

  1. hash x to index i(0..n 범위)
  2. 테이블에서 항목 i를 검색하여 값 x가 포함되어 있는지 확인합니다.

이는 256 또는 1024 값의 선형 검색보다 훨씬 빠릅니다.

합리적인 해시 함수를 찾기 위해 파이썬 코드를 작성했습니다.

바이너리 검색

256개의 "유효한" 값의 배열을 정렬하면 선형 검색이 아닌 이진 검색을 수행할 수 있습니다.즉, 256 엔트리 테이블을 8단계만으로 검색할 수 있습니다(log2(256)또는 1024 엔트리의 테이블을 10 스텝으로 설정합니다.이 경우에도 256 또는 1024 값의 선형 검색보다 훨씬 빠릅니다.

내 답이 이미 나왔다면 미안해. 그냥 내가 게으른 독자인 거야.그럼 언제든지 다운 투표해주세요)

1) 카운터 'i'를 아예 제거할 수 있습니다. 포인터를 비교하기만 하면 됩니다.

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.

이 모든 것이 큰 향상을 가져오지는 않지만, 그러한 최적화는 컴파일러 자체에 의해 달성될 수 있습니다.

2) 이미 다른 답변에서 언급했듯이, 최신 CPU는 거의 모두 RISC 기반입니다(예: ARM).최신 인텔 X86 CPU도 RISC 코어를 내장하고 있는 것으로 알고 있습니다(X86에서 온플라이로 컴파일).RISC의 주요 최적화는 파이프라인 최적화(및 인텔 및 기타 CPU)로 코드 점프를 최소화하는 것입니다.이러한 최적화의 한 가지 유형(아마도 주요 유형)은 "사이클 롤백"입니다.인텔 컴파일러도 AFAIK를 할 수 있다는 것은 매우 어리석고 효율적입니다.외관:

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:

이와 같이 최적화는 최악의 경우(어레이에 compareVal이 없는 경우)에도 파이프라인이 파손되지 않기 때문에 가능한 한 빠릅니다(물론 해시 테이블, 정렬된 어레이 등의 알고리즘 최적화는 카운트하지 않습니다).이렇게 하면 어레이 크기에 따라 더 나은 결과를 얻을 수 있습니다.참고로 사이클 롤백 접근법도 적용할 수 있습니다.다른 곳에서는 볼 수 없었던 것에 대해 이렇게 쓰고 있습니다.)

이 최적화의 두 번째 부분은 어레이 항목이 직접 주소(컴파일 단계에서 계산되고 정적 어레이를 사용하는지 확인)로 처리되므로 어레이의 기본 주소에서 포인터를 계산하는 데 추가 ADD 조작이 필요하지 않습니다.AFIK ARM 아키텍처에는 어레이 어드레싱을 고속화하는 특별한 기능이 있기 때문에 이 최적화는 큰 효과가 없을 수 있습니다.하지만 어쨌든 C코드만으로 최선을 다했다는 걸 아는 게 항상 더 낫죠?

사이클 롤백은 ROM의 낭비로 인해 어색해 보일 수 있지만(사용의 보드가 이 기능을 서포트하고 있는 경우는, RAM의 고속 부분에 올바르게 배치했습니다), 실제로는 RISC의 개념에 근거하고 있기 때문에, 속도에 대한 적절한 보수입니다.이것은 계산 최적화의 일반적인 포인트일 뿐입니다.요건에 따라서는 속도를 위해 공간을 희생하고, 그 반대도 마찬가지입니다.

1024개의 요소로 구성된 어레이에 대한 롤백이 너무 큰 희생이라고 생각되는 경우, 예를 들어 어레이를 각각 512개 항목의 2부분으로 분할하거나 4x256 등으로 분할하는 등의 '부분 롤백'을 고려할 수 있습니다.

3) 최신 CPU는 ARM NEON 명령어 세트 등의 SIMD ops를 지원하는 경우가 많습니다.이를 통해 동일한 ops를 병렬로 실행할 수 있습니다.솔직히 말하면 비교 ops에 적합한지는 기억나지 않지만, 그럴지도 모른다고 생각합니다만, 그것을 확인해 주세요.구글링에는 몇 가지 트릭이 있을 수 있습니다.최대한의 스피드를 얻으려면 , https://stackoverflow.com/a/5734019/1028256 를 참조하십시오.

새로운 아이디어를 얻을 수 있기를 바랍니다.

이것은 대답이라기보다 부록에 가깝다.

이전에도 비슷한 사례가 있었지만 상당수의 검색에서 어레이가 일정했습니다.

그 중 절반은 검색된 값이 배열에 존재하지 않았습니다.검색하기 전에 "필터"를 적용할 수 있다는 것을 깨달았습니다.

이 "필터"는 단순한 정수이며 ONCE로 계산되어 각 검색에 사용됩니다.

자바어로 되어 있지만, 매우 간단합니다.

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

바이너리 검색을 수행하기 전에 바이너리 필터를 확인합니다.

// 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 ...

}

'더 나은' 해시 알고리즘을 사용할 수 있지만, 특히 수가 많은 경우에는 매우 빠를 수 있습니다.이렇게 하면 더 많은 사이클을 절약할 수 있습니다.

다른 사용자가 이진 검색을 제공하기 위해 테이블을 재구성하거나 마지막에 센티넬 값을 추가하거나 정렬할 것을 제안했습니다.

「루프를 업이 다운 입니다).i != 0는, 「」의 유무를 보다 빠릅니다.i < 256

첫 번째 조언은 포인터 산술과 다운카운트를 없애라는 것입니다.예를 들면

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

컴파일러에게 관용적인 경향이 있습니다.루프는 관용이며 루프 변수를 통한 배열 인덱싱은 관용입니다.포인터 산술과 포인터를 사용하여 저글링을 하면 컴파일러에 대한 숙어가 난독화되어 컴파일러 라이터가 일반적인 태스크에서 가장 좋은 코스로 결정한 것보다 당신이 쓴 코드에 관련된 코드가 생성되는 경향이 있습니다.

를 들어, 위의 는 for를, 의음음음음음음음음음 running running running running running running running running running running running running running running 에서 실행되는 될 수 .-256 ★★★★★★★★★★★★★★★★★」-255, 인덱스오프, 제로&the_array[256]유효한 C에서는 표현할 수 없지만 생성 대상 머신의 아키텍처와 일치하는 것이 있을 수 있습니다.

그러니 세세한 부분까지 최적화하지 마세요.스패너를 옵티마이저의 작업에 투입하는 것에 지나지 않습니다.영리해지고 싶다면 데이터 구조 및 알고리즘에 대해 작업하되 표현을 미세하게 최적화하지는 마십시오.현재 컴파일러/아키텍처가 아닌 경우 다음 컴파일러로 돌아갑니다.

특히 어레이나 인덱스 대신 포인터 연산을 사용하는 것은 컴파일러가 얼라인먼트, 스토리지 위치, 에일리어싱 고려사항 등을 충분히 인식하고 있으며 기계 아키텍처에 가장 적합한 방법으로 강도 감소 등의 최적화를 수행하는 데 있어 독이 됩니다.

벡터화는 memchr의 구현에서 사용되는 경우가 많기 때문에 여기서 사용할 수 있습니다.다음 알고리즘을 사용합니다.

  1. OS의 비트 수(64비트, 32비트 등)와 같은 길이의 쿼리를 반복하는 마스크를 만듭니다.64비트 시스템에서는 32비트 쿼리를 2회 반복합니다.

  2. 목록을 더 큰 데이터 유형의 목록으로 만들고 값을 추출하는 것만으로 목록을 여러 데이터의 목록으로 한 번에 처리할 수 있습니다.각 청크에 대해 마스크로 XOR하고 0b0111...1로 XOR한 다음 1을 더하고 0b1000...0 마스크로 &를 반복합니다.결과가 0일 경우 일치하는 것은 절대 없습니다.그렇지 않으면 일치할 가능성이 매우 높기 때문에 일반적으로 청크를 검색합니다.

구현 예: https://sourceware.org/cgi-bin/cvsweb.cgi/src/newlib/libc/string/memchr.c?rev=1.3&content-type=text/x-cvsweb-markup&cvsroot=src

애플리케이션에 사용 가능한 메모리 용량으로 값 영역에 대응할 수 있다면 가장 빠른 해결책은 어레이를 비트 배열로 표시하는 것입니다.

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

편집

나는 비평가들의 수에 경악했다.이 스레드의 제목은 "How do I find a value is present in a carray?" 입니다.이것에 대해서는, 정확하게 대답하기 때문에, 이 답변을 지지합니다.이것이 가장 빠른 해시함수라고 주장할 수 있습니다(주소 === 값 이후).나는 댓글을 읽었고 명백한 경고에 대해 알고 있다.이러한 경고는 이 문제를 해결하는 데 사용할 수 있는 범위를 제한하지만 해결되는 문제에 대해서는 매우 효율적으로 해결됩니다.

이 답변을 완전히 거부하는 것이 아니라 해시 함수를 사용하여 속도와 성능의 균형을 더 잘 맞출 수 있는 최적의 출발점으로 간주하십시오.

CM4 Harvard 아키텍처가 최대한 활용되도록 명령어(의사 코드)와 데이터(어레이)가 별도의 메모리(RAM)에 있는지 확인합니다.사용 설명서:

여기에 이미지 설명 입력

CPU 성능을 최적화하기 위해 ARM Cortex-M4에는 명령(코드) 액세스, 데이터(D) 액세스 및 시스템(S) 액세스용 버스가 3개 있습니다.명령과 데이터를 별도의 메모리에 보관하면 코드와 데이터에 대한 액세스를 한 사이클로 동시에 수행할 수 있습니다.코드와 데이터가 같은 메모리에 보관되어 있는 경우, 데이터를 로드하거나 저장하는 명령은 2주기가 걸릴 수 있습니다.

이 가이드라인에 따라 최대 30%의 속도 향상을 관찰했습니다(내 경우는 FFT 계산).

해시 세트를 사용합니다.그러면 O(1) 조회 시간이 주어집니다.

할 수 가정합니다.0데이터에는 하지 않습니다.그렇지 않은 경우에는 솔루션을 확장할 수 있습니다.

#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);
}

이 구현 예에서는 일반적으로 검색 시간이 매우 짧지만 최악의 경우 저장된 엔트리의 수에 따라 달라질 수 있습니다.실시간 애플리케이션의 경우 이진 트리를 사용한 구현도 고려할 수 있습니다. 그러면 검색 시간을 더 쉽게 예측할 수 있습니다.

테이블 내의 상수 집합을 미리 알고 있는 경우 완벽한 해시를 사용하여 테이블에 대한 액세스를 1개만 수행할 수 있습니다.완벽한 해싱은 모든 대상 키를 고유한 슬롯에 매핑하는 해시 함수를 결정합니다(이 테이블은 항상 고밀도는 아니지만, 일반적으로 밀도가 낮은 테이블로 인해 해싱 함수가 단순해집니다).

일반적으로 특정 키 집합에 대한 완벽한 해시 함수는 비교적 쉽게 계산할 수 있습니다. 여러 개의 프로브를 사용하는 데 시간을 두고 경쟁하기 때문에 길고 복잡한 해시 함수는 원하지 않습니다.

완벽한 해시는 "1 프로브 최대" 방식입니다.k개의 프로브를 만드는 데 걸리는 시간과 해시 코드 계산의 단순성을 교환해야 한다는 생각으로 아이디어를 일반화할 수 있다.결국 목표는 가장 적은 프로브나 가장 단순한 해시 함수가 아닌 "최소 총 조회 시간"입니다.하지만 k-probes-max 해시 알고리즘을 만드는 사람은 본 적이 없습니다.누군가 할 수 있을 것 같지만, 그건 연구일 가능성이 큽니다.

다른 하나는 프로세서가 매우 빠를 경우 완벽한 해시에서 메모리로의 프로브가 실행 시간을 지배한다는 것입니다.프로세서의 속도가 그다지 빠르지 않은 경우 k>1 프로브보다 실용적일 수 있습니다.

프로세서가 LPC4357의 최대치인 204MHz로 동작하고 타이밍 결과가 평균 케이스(트래버된 어레이의 절반)를 반영한다고 가정하면 다음과 같은 결과가 나옵니다.

  • CPU 주파수: 204MHz
  • 사이클 주기: 4.9 ns
  • 지속시간(사이클): 12.5µs/4.9ns = 2551사이클
  • 반복당 사이클: 2551 / 128 = 19.9

따라서 검색 루프는 반복마다 약 20 사이클을 소비합니다.그렇게 나쁘진 않지만, 더 빨리 하려면 조립품을 봐야 할 것 같아요.

포인터를 합니다.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;
}

적어도 시험해 볼 만 하군

저는 해시의 광팬입니다.물론 문제는 고속이면서 최소한의 메모리(특히 임베디드 프로세서)를 사용하는 효율적인 알고리즘을 찾는 것입니다.

발생할 수 있는 값을 미리 알고 있다면 여러 알고리즘을 통해 최적의 알고리즘 또는 데이터에 대한 최적의 파라미터를 찾는 프로그램을 만들 수 있습니다.

저는 이 투고에서 읽을 수 있는 프로그램을 만들어 매우 빠른 결과를 얻었습니다.16000개의 엔트리는 대략 2^14 또는 평균 14개의 비교로 변환되어 바이너리 검색을 사용하여 값을 구합니다.저는 <=1.5 lookups>에서 평균 값을 찾는 매우 빠른 lookup을 목표로 하고 있었습니다.그 결과 RAM 요건이 커졌습니다.좀 더 보수적인 평균값(=3)으로 많은 메모리를 절약할 수 있다고 생각합니다.256 또는 1024 엔트리의 바이너리 검색의 평균 케이스는 각각 평균 8과 10이 됩니다.

평균 검색에서는 범용 알고리즘(변수로 1개의 나눗셈을 사용)을 사용하여 약 60사이클(Intel i5 탑재 노트북)과 특수 알고리즘(아마 곱셈을 사용했을 가능성이 있음)을 사용하여 40~45사이클이 필요했습니다.이는 MCU가 실행되는 클럭 주파수에 따라 마이크로초 미만의 검색 시간으로 변환됩니다.

엔트리 어레이가 엔트리에 액세스한 횟수를 추적하면 실제 상황을 더욱 정확하게 파악할 수 있습니다.엔트리의 배열이 엔트리가 계산되기 전에 액세스 빈도가 가장 높은 것부터 가장 낮은 것까지 정렬되어 있는 경우는, 1회의 비교로 가장 빈번하게 발생하는 값을 찾을 수 있습니다.

언급URL : https://stackoverflow.com/questions/25661925/quickly-find-whether-a-value-is-present-in-a-c-array

반응형