가능한 한 빨리 5 개의 작은 정수 중 가장 큰 두 개 찾기


9

소형 임베디드 시스템의 이미지 데이터에 5 개의 교차 중앙값 필터를 사용합니다.

    x
  x x x
    x

이 알고리즘은 실제로 간단합니다. 5 개의 부호없는 정수 값을 읽고, 가장 높은 2를 얻고, 그에 대한 계산을 수행하고, 부호없는 정수 결과를 다시 씁니다.

좋은 점은 5 개의 정수 입력 값이 모두 0-20의 범위에 있다는 것입니다. 계산 된 정수 값도 0-20 범위에 있습니다!

프로파일 링을 통해 가장 큰 두 숫자를 얻는 것이 병목 현상임을 알았 으므로이 부분의 속도를 높이고 싶습니다. 이 선택을 수행하는 가장 빠른 방법은 무엇입니까?

현재 알고리즘은 5 개의 숫자로 지정된 위치에 1을 가진 32 비트 마스크와 HW 지원 CLZ 기능을 사용합니다.
CPU는 회사 외부에서는 사용할 수없는 독점적 인 CPU라고 말해야합니다. 내 컴파일러는 GCC이지만이 CPU에 맞게 조정되었습니다.

조회 테이블을 사용할 수 있는지 알아 내려고 시도했지만 사용할 수있는 키를 생성하지 못했습니다.

내가 가진 입력에 대한 조합을하지만 순서는 중요하지 않습니다, 즉 동일하다 .215[5,0,0,0,5][5,5,0,0,0]

아래의 해시 함수는 충돌없이 완벽한 해시를 생성합니다!

def hash(x):
    h = 0
    for i in x:
        h = 33*h+i
    return h

그러나 해시는 엄청 나며이를 사용할 메모리가 충분하지 않습니다.

사용할 수있는 더 나은 알고리즘이 있습니까? 조회 테이블을 사용하고 키를 생성하여 내 문제를 해결할 수 있습니까?


1
현재 어떤 알고리즘을 사용하고 있습니까? 7 개의 정수 비교로 충분합니다. 너무 느립니까? 여러분은 hash이미 많은 작업을 수행합니다. 메소드에 대한 후속 호출이 관련되어 있습니까? 예를 들어 중앙 x이 매트릭스를 통해 행 단위로 이동합니까?
Raphael

필터는 이미지를 통해 행 단위로 얽혀 있습니다. 즉 5 값을 얻고 계산을 한 다음 모든 것을 한 단계 오른쪽으로 이동하고 반복하십시오. 해시는 예일뿐입니다. 데이터 읽기를 최소화하기 위해 여러 슬라이딩 창 솔루션을 벤치마킹했지만 모두 가장 높은 2 값을 찾는 것으로 요약됩니다.
Fredrik Pihl

3
알고리즘이 제대로 구현되면 계산이 아닌 메모리 액세스에 의해 제한 될 가능성이 높습니다. 해시 테이블을 사용하면 메모리 액세스 량이 증가하고 속도가 느려집니다. 현재 코드를 게시하여 개선 방법을 확인할 수 있습니다. 미세 최적화 만 가능하다고 생각합니다. 내가 생각할 수있는 가장 가까운 것은 : 아마도 우리는 이웃 창 사이에 2 개의 값이 공통적이라는 사실을 활용할 수 있습니까?
jkff

@jkff 행렬, 캐시 크기 및 (캐시) 매핑 기능에 따라 모든 값을 한 번만로드하면됩니다. 대부분의 작업은 레지스터 또는 L1 캐시에서 실행해야합니다. 그러나 파이프 라이닝은 또 다른 문제입니다.
Raphael

1
그건 그렇고, 이미 병렬로 수행합니까? 이는 벡터 병렬화 또는 SIMD (예 : GPU)에 특히 적합합니다. 이 경로는 셀당 몇 퍼센트를 절약하는 것보다 훨씬 더 도움이 될 것입니다.
Raphael

답변:


11

내에서 다른 대답 나는 조건부 점프 효율성의 주요 장애물이 될 가능성이 있음을 시사하는 것이다. 결과적으로, 정렬 네트워크 는 데이터에 무관하며, 입력에 상관없이 동일한 순서의 비교가 실행되며 스왑 만 조건부로 실행됩니다.

물론 정렬은 너무 많은 작업 일 수 있습니다. 가장 큰 두 숫자 만 있으면됩니다. 운 좋게도, 선택 네트워크도 연구되었습니다. Knuth는 5² 중에서 가장 작은 두 숫자를 찾는 것은 비교 [1, 5.3.4 ex 19] (및 최대 스왑) 로 수행 할 수 있다고 말합니다 .U^2(5)=6

그가 솔루션에서 제공하는 네트워크 (0 기반 배열로 다시 작성)는

[0:4][1:4][0:3][1:3][0:2][1:2]

비교 방향을 조정 한 후 의사 코드에서 다음과 같이 구현합니다.

def selMax2(a : int[])
  a.swap(0,4) if a[0] < a[4]
  a.swap(1,4) if a[1] < a[4]
  a.swap(0,3) if a[0] < a[3]
  a.swap(1,3) if a[1] < a[3]
  a.swap(0,2) if a[0] < a[2]
  a.swap(1,2) if a[1] < a[2]
  return (a[0], a[1])
end

이제 순진한 구현에는 여전히 스왑 코드에서 조건부 점프가 있습니다. 그러나 컴퓨터에 따라 조건부 지침을 사용하여 Cirumvent 할 수 있습니다. x86은 일반적인 머드 핏 자아 인 것 같습니다. 대부분의 작업은 조건부 이기 때문에 ARM이 더 유망 해 보입니다 . 지시 사항을 올바르게 이해하면 배열 값이 다음을 R0통해 레지스터에로드되었다고 가정하고 첫 번째 스왑이 이것을 변환합니다 R4.

CMP     R0,R4
MOVLT   R5 = R0
MOVLT   R0 = R4
MOVLT   R4 = R6

예, 물론 XOR 스와핑EOR 과 함께 사용할 수 있습니다 .

프로세서에 이와 비슷한 것이 있기를 바랍니다. 당신이 경우 물론, 구축 이 목적을 위해 일을, 어쩌면 당신이 거기에 하드 유선 네트워크를받을 수 있나요?

이것은 아마도 당신이 고전 영역에서 할 수있는 최선의 방법 일 것입니다.


  1. 정렬 및 검색 : Donald E. Knuth; 컴퓨터 프로그래밍 Vol. 3 (1998 년 2 판)
  2. 이렇게하면 선택된 두 요소가 정렬되지 않은 상태로 유지됩니다. 이들을 주문하려면 추가 비교가 필요합니다. 즉, 총합 은 개입니다 [1, p234 표 1].W^2(5)=7

나는 이것을 받아들이고있다. 계속 진행하기 전에 벤치마킹해야 할 많은 새로운 아이디어를 받았습니다. Knuth를 참조하면 항상 나를 위해 일합니다 :-) 노력과 시간을 주셔서 감사합니다!
Fredrik Pihl 2019

@FredrikPihl Cool, 결국 결과가 어떻게되는지 알려주십시오!
Raphael

나는 할 것이다! 지금 5.3.3 장을 읽으십시오. 루이스 캐롤에 대한 참조와 그것의 시작과 테니스 대회 :-) 사랑
프레드릭 Pihl

2
명령어 세트에 따라 선택 네트워크와 함께 2 * max (a, b) = a + b + abs (ab)를 사용하는 것이 유용 할 수 있습니다. 예상치 못한 조건부 점프보다 비용이 적게들 수 있습니다 (abs에 대한 본질적 또는 조건부 이동이없는 경우에도 : gcc, x86의 경우 x86에 의존하지 않는 점프없는 시퀀스 생성). 점프없는 시퀀스는 SIMD 또는 GPU와 결합 할 때도 유용합니다.
AProgrammer

1
선택 네트워크 (정렬 네트워크와 같은)는 병렬 작업이 가능합니다. 구체적으로, 지정된 선택 네트워크에서, 비교 1 : 4 및 0 : 3은 병렬로 수행 될 수 있고 (프로세서, 컴파일러 등이 효율적으로 지원하는 경우), 비교 1 : 3 및 0 : 2는 병렬로 수행 될 수있다.
브루스 릴리

4

그것이 테이블에 있도록 직접 알고리즘은 다음과 같습니다.

// Sort x1, x2
if x1 < x2
  M1 = x2
  m1 = x1
else
  M1 = x1
  m1 = x2
end

// Sort x3, x4
if x3 < x4
  M2 = x4
  m2 = x3
else
  M2 = x3
  m2 = x4
end

// Pick largest two
if M1 > M2
  M3 = M1
  if m1 > M2
    m3 = m1
  else
    m3 = M2
  end
else
  M3 = M2
  if m2 > M1
    m3 = m2
  else
    m3 = M1
  end
end

// Insert x4
if x4 > M3
  m3 = M3
  M3 = x4
else if x4 > m3
  m3 = x4
end

의 영리한 구현을 if ... else통해 직접 번역이 가질 수있는 무조건적인 점프를 제거 할 수 있습니다.

이것은 못 생겼지 만

  • 5-6 번의 비교 (즉, 조건부 점프)
  • 9-10 개의 할당 (11 개의 변수, 모두 레지스터에 있음)
  • 추가 메모리 액세스가 없습니다.

실제로, [1]의 섹션 5.3.3에서 정리 S가 보여 주듯이 6 개의 비교가이 문제에 대해 최적이다; 여기에 가 필요합니다 .W2(5)

그러나 파이프 라이닝을 사용하는 기계에서는 이것이 빠를 것으로 예상 할 수 없습니다. 조건부 점프의 비율이 높기 때문에 대부분의 시간이 실속에 소비됩니다.

보다 간단한 변형 (정렬 x1x2그 다음에 다른 값 삽입)은 4 ~ 7 개의 비교와 5 ~ 6 개의 할당 만 수행합니다. 나는 점프가 여기에서 더 높은 비용이들 것으로 기대하기 때문에 나는 이것을 고수했다.


  1. 정렬 및 검색 : Donald E. Knuth; 컴퓨터 프로그래밍 Vol. 3 (1998 년 2 판)

최적화 컴파일러가 이것으로 무엇을 할 수 있는지 궁금합니다.
Raphael

이를 구현하고 현재 CLZ 기반 솔루션에 대해 벤치마킹하겠습니다. 시간 내 줘서 고마워!
Fredrik Pihl

1
@FredrikPihl 벤치 마크 결과는 무엇입니까?
Raphael

1
SWAP 기반 접근 방식이 CLZ를 능가합니다! 지금 모바일에서. 이제 모바일에서 더 많은 데이터 또 다른 시간을 게시 할 수
프레드릭 Pihl

@FredrikPihl 쿨! 나는 좋은 오래된 이론 접근법이 (여전히) 실용적이 될 수있어서 기쁘다. :)
라파엘

4

이것은 Souper 프로젝트를 위한 훌륭한 응용 및 테스트 사례가 될 수 있습니다 . Souper는 짧은 코드 시퀀스를 입력으로 사용하여 최대한 최적화하려고 시도하는 도구 인 수퍼 옵티 마이저입니다.

수퍼는 오픈 소스입니다. 코드 스 니펫에서 Souper를 실행하여 더 잘 할 수 있는지 확인할 수 있습니다.

16 개의 4 비트 값을 정렬하는 빠른 코드 작성에 대한 John Regehr의 컨테스트를 참조하십시오 . 일부 기술이 유용 할 수 있습니다.


OP가 시도한 프로그램에서 이것이 무엇을 할 수 있는지에 관심이 있습니다.
Raphael

3

당신은 사용할 수 있습니다 213세 개의 정수를 가져 와서 가장 큰 두 개를 출력하는 테이블. 그런 다음 세 가지 테이블 조회를 사용할 수 있습니다.

T[T[T[441*a+21*b+c]*21+d]*21+e]

마찬가지로 214 테이블에서 두 개의 테이블 조회로 줄일 수 있지만 이것이 더 빠를지는 확실하지 않습니다.

작은 테이블을 정말로 원한다면 두 가지를 사용할 수 있습니다 212두 개의 숫자를 "정렬"하고 정렬 네트워크를 사용합니다. Wikipedia 에 따르면 최대 18 개의 테이블 조회 (9 개의 비교기)가 필요합니다. (1) 두 개의 가장 큰 요소 만 알고 싶고 (2) 일부 비교기 게이트의 경우 최대 값에만 관심이있을 수 있으므로 더 적은 비용으로 할 수 있습니다.

당신은 또한 하나를 사용할 수 있습니다 212표. 정렬 네트워크를 구현하면 메모리 액세스는 적지 만 산술은 더 많이 사용됩니다. 이 방법으로 최대 9 개의 테이블 조회를 얻을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.