가장 적은 양의 명령에서 가장 빠른 정수 제곱근
나는 명시적인 분할을 수반하지 않는 빠른 정수 제곱근이 필요합니다. RISC 는 Δ RISC Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ,add,mul,sub,shift한 사이클(음 - 연산의 결과는 세 번째 사이클로 작성됩니다, 정말 - 하지만 인터리빙이 있습니다)에서 이러한 ops를 사용하고 빠른 Ingeter 알고리즘은 매우 감사하겠습니다.
이것이 제가 지금 가지고 있는 것이고, 다음 루프는 (값에 관계없이) 매 번 16번 실행되기 때문에 바이너리 검색이 더 빨라야 한다고 생각합니다.아직 광범위하게 디버깅하지는 않았으므로(하지만 곧) 조기 종료가 가능할 수도 있습니다.
unsigned short int int_sqrt32(unsigned int x)
{
unsigned short int res=0;
unsigned short int add= 0x8000;
int i;
for(i=0;i<16;i++)
{
unsigned short int temp=res | add;
unsigned int g2=temp*temp;
if (x>=g2)
{
res=temp;
}
add>>=1;
}
return res;
}
[대상 RISC의 맥락에서] 위의 현재 성능 비용은 5개 명령의 루프(비트셋, 멀, 비교, 저장, 시프트)인 것 같습니다.완전히 롤을 풀 수 있는 캐시 공간이 없을 수도 있습니다. 하지만 부분적으로 롤을 풀 수 있는 가장 유력한 후보가 될 것입니다.16]이 아닌 4개의 루프를 사용합니다.따라서 비용은 16*5 = 80개의 명령어(롤링되지 않은 경우 루프 오버헤드까지 포함)입니다.완전히 인터리브된 경우에는 80회(마지막 지침의 경우 2회)의 사이클만 소요됩니다.
82 사이클 이하에서 다른 sqrt 구현(add, mul, bitshift, store/cmp만 사용)을 수행할 수 있습니까?
FAQ:
- 좋은 빠른 코드를 만들기 위해 컴파일러에 의존하는 것은 어떨까요?
플랫폼용으로 작동하는 C → RISC 컴파일러가 없습니다.현재 레퍼런스 C 코드를 수기로 작성한 RISC ASM으로 포팅하겠습니다.
- 코드 프로파일링을 해봤나요?
sqrt병목 현상이 실제로 일어나는 것입니까?
아니요, 그럴 필요는 없습니다.타겟 RISC 칩은 약 20 MHz이므로 모든 명령어가 계산됩니다.코어 루프(슈터와 수신기 패치 사이의 에너지 전달 폼 팩터 계산), 여기서,sqrt사용되며, 렌더링 프레임마다 1,000회(물론 충분히 빠르다고 가정하더라도), 초당 최대 60,000회, 전체 데모의 경우 약 1,000,000회 실행됩니다.
- 하여 을 을 하려고 하여를 제거하려고 ?
sqrt?
요. , .사실 저는 2명을 없앴습니다.sqrt이미 많은 분할(시프팅으로 대체되거나 대체됨).(기준 플로트 버전에 비해) 기가헤르츠 노트북에서도 엄청난 성능 향상을 볼 수 있습니다.
- 응용 프로그램은 무엇입니까?
이것은 컴포지트 데모를 위한 실시간 프로그레시브-정제 라디오시티 렌더입니다.각 프레임마다 한 번의 촬영 주기를 갖도록 하는 것이 좋습니다. 예를 들어 초당 60번의 촬영이 가능하지만, SW 래스터라이저가 그렇게 빠르지는 않을 것입니다. [하지만 적어도 RISC와 병렬로 다른 칩에서 실행될 수 있습니다.] 장면을 렌더링하는 데 2-3개의 프레임이 필요하다면 RISC가 작동했을 것입니다.2-3 프레임의 무선성 데이터를 통해, 병렬로]).
- 대상 ASM에서 직접 작업을 해보는 것은 어떨까요?
왜냐하면 무선성은 약간 관련된 알고리즘이고 Visual Studio의 즉각적인 편집 및 계속 디버깅 기능이 필요하기 때문입니다.지난 주말 VS에서 수행한 작업(부동점 수학을 정수 전용으로 변환하기 위해 수백 번의 코드 변경)으로 대상 플랫폼에서 인쇄 디버깅만 수행하면 6개월이 걸릴 것입니다.
- 왜 사단을 못 쓰시나요?
대상 RISC에서 mul, add, sub, shift, 비교, load/store(1주기만 소요됨)의 16배 속도가 느리기 때문입니다.따라서 절대적으로 필요한 경우에만 사용됩니다(불행하게도 시프트를 사용할 수 없는 경우는 이미 여러 번 있습니다).
- 검색대를 사용할 수 있습니까?
엔진은 이미 다른 LUT를 필요로 하며 메인 RAM에서 RISC로 복사하는 것은 비용이 엄청나게 많이 듭니다(각 프레임과 모든 프레임은 아님).Δ100~200% Δ128~256 Δ100~200% ~ Δ100~200% 할 수 입니다.sqrt.
- 의 는 입니까
sqrt?
서명되지 않은 32비트 int(4,294,967,295)로 줄일 수 있었습니다.
EDIT1: 대상 RISC ASM에 두 가지 버전을 포팅했기 때문에 실행 중(테스트 장면에 대한) ASM 지침의 정확한 수를 알 수 있습니다.
호출 : sqrt회: 2,800회
방법1:이 게시물에서도 동일한 방법(루프 실행 16회)
방법2: fred_sqrt (http://www.azillionmonkeys.com/qed/sqroot.html) 에서 3c)
1: sqrt 1: 152.98 sqrt
방법 2: sqrt당 39.48 명령(최종 반올림 및 뉴턴 반복 사용)
방법2: sqrt당 21.01개의 지시 (최종 반올림 및 2개의 뉴턴 반복 제외)
메서드 2는 256개의 값을 가진 LUT를 사용하지만 대상 RISC는 4KB 캐시 내에서 32비트 액세스만 사용할 수 있으므로 실제로는 256*4 = 1KB가 필요합니다.하지만 성능을 고려할 때 그 1KB는 (4개 중) 아껴야 할 것 같습니다.
또한 Final 라운딩을 비활성화할 때 가시적인 시각적 아티팩트가 없다는 것과 (Method2의) 마지막에 두 번의 뉴턴 반복이 있다는 것을 발견했습니다.LUT의 정확성은 충분히 뛰어나다는 뜻입니다.누가 알았겠어요?
최종 비용은 sqrt당 21.01개의 명령으로, 첫 번째 솔루션보다 거의 ~10배 더 빠릅니다. 큰 ) 가능하기 다른 라벨로 의 경우에는 비교합니다와.직접 상대 점프는 ~10개의 명령 내에서만 가능하므로(가장 안쪽의 2개 조건을 제외하고는 이러한 큰 if-then-simplic chain과 호환됨) 다른 라벨로 점프하는 경우와 세컨드.
EDIT2:ASM 마이크로 최적화
벤치마킹을 하면서 26개 If마다 카운터를 추가했습니다.그러면. 다른 코드블록들은 가장 자주 실행되는 블록이 없는지 확인해보세요.블록 0/10/11은 99.57%/99.57%/92.57%의 경우에 실행되는 것으로 나타났습니다.를 들어, $1 r24= $10. (3π)해 3π (32π)예 3π (32π)다 3π (ω: r26= $1.0000 r25= $100.0000 r24= $10.0000)수 3π (ω: r26= $1.000000 r24= $10.0000)의 (중) 하는 것을 할 수 합니다.
를 통해 총812)에서 . Δ58,812(평균 21.01) Δ50,448(평균 18.01) Δ.
따라서 현재 평균 sqrt 비용은 18.01 ASM 명령어에 불과하지만(그리고 분할은 없습니다!) 인라인 처리를 해야 합니다.
EDIT3: ASM 마이크로 최적화
이 3개의 블록(0/10/11)이 가장 자주 실행된다는 것을 알고 있으므로, 이러한 특정 조건에서 로컬 짧은 점프(양방향으로 16바이트)를 사용할 수 있습니다. 이는 일반적으로 몇 개의 명령에 불과하며(따라서 대부분 쓸모가 없음), 특히 조건 중에 6바이트 MOVI #jump_label, 레지스터가 사용되는 경우).물론, 다른 조건에서는 2개의 작업이 추가로 발생하지만(그렇지 않으면 발생하지 않을 것입니다), 그럴 만한 가치가 있습니다.블록 10을 스왑(그러면 다른 블록과 블록)해야 하므로 읽기와 디버그가 더 어려워질 것입니다. 하지만 그 이유를 자세히 설명했습니다.
현재 (테스트 장면에서) 총 명령어 비용은 42,500에 불과하며, sqrt당 평균 15.18개의 ASM 명령어가 있습니다.
EDIT4:ASM 마이크로 최적화
블록 11 조건이 가장 안쪽의 블록 12&13으로 분할됩니다.필요한 비트 시프트 왼쪽 #2에 비트 시프트 오른쪽을 병합하면(캐시 내의 모든 오프셋이 32비트여야 하므로), 블록에 +1 산술 연산이 필요하지 않기 때문에 로컬 쇼트 점프가 실제로 Else 블록에 도달할 수 있습니다.이렇게 하면 점프 레지스터를 채우는 것이 절약되지만, 비교 값 $40.000을 위해 레지스터 23을 하나 더 희생해야 합니다.
최종 비용은 34,724개의 명령어와 sqrt당 평균 12.40개의 ASM 명령어입니다.
또한 조건의 순서를 변경할 수 있다는 것을 깨닫고 있습니다. (다른 범위의 작업 비용이 더 많이 들지만, 이는 경우의 ~7%에서만 발생합니다.) 이 특정 범위($10.000, $40.000)를 먼저 선호하여 최소 1개 또는 최대 2개의 조건을 절약할 수 있습니다.
이 경우 sqrt당 ~8.40까지 떨어져야 합니다.
빛의 세기와 벽까지의 거리에 따라 그 범위가 결정된다는 것을 실감하고 있습니다. 나는 의 RGB 으로 통제합니다즉,의 RGB과의를로할다수다수할g과,로rt즉ei,의lef를의btde가능한 한 포괄적인 솔루션을 원하지만, 이 실현(sqrt당 ~12ops)을 고려하면 이 정도로 빠른 속도로 sqrt를 얻을 수 있다면 밝은 색상의 유연성을 기꺼이 희생하겠습니다.게다가 전체 데모에는 약 10-15개의 다른 조명이 있으므로 결국 동일한 sqrt 범위를 생성하지만 엄청나게 빠른 sqrt를 얻을 수 있는 색상 조합을 간단히 찾을 수 있습니다.물론 그럴만한 가치가 있습니다.그리고 일반 폴백(전체 범위에 걸쳐 있는)은 여전히 잘 작동합니다.두 세계에서 최고죠, 정말로.
여기 한번 보세요.
예를 들어, 3(a)에는 64->32 비트 제곱근을 수행하는 데 약간 적응할 수 있고, 어셈블러에도 약간 전사할 수 있는 이 메서드가 있습니다.
/* by Jim Ulery */
static unsigned julery_isqrt(unsigned long val) {
unsigned long temp, g=0, b = 0x8000, bshft = 15;
do {
if (val >= (temp = (((g << 1) + b)<<bshft--))) {
g += b;
val -= temp;
}
} while (b >>= 1);
return g;
}
분할, 곱셈, 비트 이동만 없습니다.그러나 특히 분기를 사용하는 경우 소요 시간은 다소 예측할 수 없을 것입니다(ARM RISC 조건부 지침에 따라 작동).
일반적으로 이 페이지에는 제곱근을 계산하는 방법이 나열되어 있습니다.빠른 역제곱근(즉, 역제곱근)을 생성하려는 경우.x**(-0.5)또는 코드를 최적화하는 놀라운 방법에 관심이 있습니다. 이것, 이것, 이것을 보십시오.
은 당신의 것과 ops의 (의 코드에 and 은 의 과 를 하지만 가 는 의 을 와 합니다 의 에 를 는 가 을 와 합니다 은 i포루프와 3개 과제에서, ASM에서 코드화되면 그 중 일부가 사라지는 것은 아닐까요?에는 6개의 , 래 6개의 ops를 g*g>n2개(배정 없음)로 합니다.
int isqrt(int n) {
int g = 0x8000;
int c = 0x8000;
for (;;) {
if (g*g > n) {
g ^= c;
}
c >>= 1;
if (c == 0) {
return g;
}
g |= c;
}
}
여기 있습니다.루프를 풀고 입력에서 0이 아닌 비트 중 가장 높은 비트를 기준으로 적절한 지점으로 점프하면 비교를 제거할 수 있습니다.
갱신하다
뉴턴의 방법을 사용하는 것에 대해 더 생각해 보았습니다.이론적으로 각 반복에 대해 정확도 비트 수는 두 배로 증가해야 합니다.이는 정답 비트가 거의 없을 때 뉴턴의 방법이 다른 어떤 제안보다 훨씬 더 나쁘다는 것을 의미합니다. 하지만 정답 비트가 많이 있을 때 상황이 바뀝니다.대부분의 제안이 비트당 4 사이클이 소요되는 것으로 보인다는 점을 고려하면, 4 비트 이상을 주지 않는 한 뉴턴 방법의 한 번의 반복(분할 시 16 사이클 + 덧셈 시 1 + 쉬프트 = 18 사이클)은 가치가 없다는 것을 의미합니다.
따라서 제안하는 방법 중 하나(8*4 = 32 cycle)로 8비트의 답을 생성한 후 뉴턴 방법(18 cycle)을 한 번 반복하여 비트 수를 16으로 두 배로 늘리자는 것입니다.이는 총 50개의 사이클입니다. (게다가 뉴턴의 방법을 적용하기 전에 9비트를 얻기 위해 4개의 사이클을 추가하고, 뉴턴의 방법에서 종종 발생하는 오버슈트를 극복하기 위해 2개의 사이클을 추가할 수도 있습니다.)이는 최대 56주기로 다른 제안들과 비교할 수 있습니다.
2차 업데이트
저는 하이브리드 알고리즘 아이디어를 코딩했습니다.뉴턴의 방법 자체는 오버헤드가 없습니다. 단지 유효 자릿수를 적용하고 두 배로 늘리면 됩니다.문제는 뉴턴 방법을 적용하기 전에 예측 가능한 유효 자릿수를 갖는 것입니다.이를 위해서는 가장 중요한 부분의 답이 어디에 나타날지 파악해야 합니다.다른 포스터에 의해 주어진 빠른 DeBruijn 시퀀스 방법의 수정을 사용하여, 우리는 제 추정에서 약 12 사이클로 그 계산을 수행할 수 있습니다.반면에 답의 msb 위치를 알면 모든 방법(최악의 경우가 아닌 평균)이 빨라지기 때문에 무슨 일이 있어도 가치가 있어요.
답의 msb를 계산한 후 위에 제시한 알고리즘을 여러 라운드 실행한 후 뉴턴 방법을 한두 라운드로 마칩니다.우리는 다음 계산을 통해 뉴턴 방법을 실행할 시기를 결정합니다. 답의 1비트는 댓글의 계산에 따라 약 8주기가 소요되고, 뉴턴 방법의 1라운드는 약 18주기(분할, 덧셈, 쉬프트, 그리고 아마도 할당)가 소요되므로 적어도 3비트를 실행하려면 뉴턴 방법을 실행해야 합니다.따라서 6비트 답변의 경우 선형 방법을 3번 실행하여 3개의 유효 비트를 얻은 후 뉴턴 방법을 1번 실행하여 다른 3개를 얻을 수 있습니다.15비트 답변의 경우 선형 방법을 4번 실행하여 4비트를 얻고 뉴턴 방법을 2번 실행하여 다른 4비트를 얻고 다른 7비트를 얻습니다.뭐 이런 거.
이러한 계산은 선형 방법으로 비트를 얻는 데 필요한 사이클 수와 뉴턴 방법으로 필요한 사이클 수를 정확히 아는 것에 달려 있습니다.예를 들어 선형 방식으로 비트를 쌓는 더 빠른 방법을 발견함으로써 "경제학"이 바뀌면 뉴턴의 방법을 언제 사용할지에 대한 결정이 달라질 것입니다.
루프를 풀고 결정을 스위치로 구현했는데, 조립 시 빠른 테이블 조회가 가능하길 바랍니다.각각의 경우에 최소 사이클 수가 있는지 확실하지 않아 추가 튜닝이 가능할 수도 있습니다.예를 들어, s=10의 경우 5비트를 구하고 뉴턴의 방법을 두 번이 아니라 한 번 적용할 수 있습니다.
나는 알고리즘이 정확한지 철저히 테스트했습니다.경우에 따라 약간 틀린 답을 받아들일 의사가 있다면 약간의 추가적인 속도 향상이 있습니다.Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ ΔΔ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δm^2-1이 그수 에 대해 하는 데 됩니다. 그리고 알고리즘이 그 입력을 처리할 수 없기 때문에, 사이클은 처음에 입력 0에 대해 테스트하는 데 사용됩니다.만약 당신이 0의 제곱근을 취하지 않을 것이라는 것을 안다면, 당신은 그 테스트를 제거할 수 있습니다.마지막으로, 답에 8개의 유효한 비트만 필요하다면 뉴턴의 방법 계산 중 하나를 떨어뜨릴 수 있습니다.
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
uint32_t isqrt1(uint32_t n);
int main() {
uint32_t n;
bool it_works = true;
for (n = 0; n < UINT32_MAX; ++n) {
uint32_t sr = isqrt1(n);
if ( sr*sr > n || ( sr < 65535 && (sr+1)*(sr+1) <= n )) {
it_works = false;
printf("isqrt(%" PRIu32 ") = %" PRIu32 "\n", n, sr);
}
}
if (it_works) {
printf("it works\n");
}
return 0;
}
/* table modified to return shift s to move 1 to msb of square root of x */
/*
static const uint8_t debruijn32[32] = {
0, 31, 9, 30, 3, 8, 13, 29, 2, 5, 7, 21, 12, 24, 28, 19,
1, 10, 4, 14, 6, 22, 25, 20, 11, 15, 23, 26, 16, 27, 17, 18
};
*/
static const uint8_t debruijn32[32] = {
15, 0, 11, 0, 14, 11, 9, 1, 14, 13, 12, 5, 9, 3, 1, 6,
15, 10, 13, 8, 12, 4, 3, 5, 10, 8, 4, 2, 7, 2, 7, 6
};
/* based on CLZ emulation for non-zero arguments, from
* http://stackoverflow.com/questions/23856596/counting-leading-zeros-in-a-32-bit-unsigned-integer-with-best-algorithm-in-c-pro
*/
uint8_t shift_for_msb_of_sqrt(uint32_t x) {
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return debruijn32 [x * 0x076be629 >> 27];
}
uint32_t isqrt1(uint32_t n) {
if (n==0) return 0;
uint32_t s = shift_for_msb_of_sqrt(n);
uint32_t c = 1 << s;
uint32_t g = c;
switch (s) {
case 9:
case 5:
if (g*g > n) {
g ^= c;
}
c >>= 1;
g |= c;
case 15:
case 14:
case 13:
case 8:
case 7:
case 4:
if (g*g > n) {
g ^= c;
}
c >>= 1;
g |= c;
case 12:
case 11:
case 10:
case 6:
case 3:
if (g*g > n) {
g ^= c;
}
c >>= 1;
g |= c;
case 2:
if (g*g > n) {
g ^= c;
}
c >>= 1;
g |= c;
case 1:
if (g*g > n) {
g ^= c;
}
c >>= 1;
g |= c;
case 0:
if (g*g > n) {
g ^= c;
}
}
/* now apply one or two rounds of Newton's method */
switch (s) {
case 15:
case 14:
case 13:
case 12:
case 11:
case 10:
g = (g + n/g) >> 1;
case 9:
case 8:
case 7:
case 6:
g = (g + n/g) >> 1;
}
/* correct potential error at m^2-1 for Newton's method */
return (g==65536 || g*g>n) ? g-1 : g;
}
제 기계에 대한 가벼운 테스트(인정하건대 당신의 것과는 전혀 다른)에서, 새 제품은isqrt1이다로약더다게%더e게약tn0snr%다이로s0isqrt내가 준 일상적인 일.
곱셈이 동일한 속도(또는!보다 빠름)의 덧셈 및 이동이거나 레지스터에 포함된 양별 빠른 이동 지침이 없는 경우 다음 사항은 도움이 되지 않습니다.그렇지 않은 경우:
당신은 요를 계산하고 .temp*temp각 고침이 프다침로각침로f,다t프각enptemp = res | add, 와 입니다.res + add겹치지 (의기에지다미은고가다미은(고eyar,d의에가ns기,e지u(res*res프서고,(고b) (b)add특별한 구조를 가지고 있습니다. (항상 단일 비트에 불과합니다.)서,그을서서etot그,g서,서o(a+b)^2 = a^2 + 2ab + b^2, 그리고 당신은 이미a^2,그리고.b^2 비트일다로두배리을다가일을다배setaosser지dte일-두가일로etb,그리고.2ab정당합니다a의다개더은로로동d로nttney-ef1nd의1다로은b 을 할 할 을 제거할 수 .
unsigned short int int_sqrt32(unsigned int x)
{
unsigned short int res = 0;
unsigned int res2 = 0;
unsigned short int add = 0x8000;
unsigned int add2 = 0x80000000;
int i;
for(i = 0; i < 16; i++)
{
unsigned int g2 = res2 + (res << i) + add2;
if (x >= g2)
{
res |= add;
res2 = g2;
}
add >>= 1;
add2 >>= 2;
}
return res;
}
또한 같은 타입을 사용하는 것이 더 좋을 것 같습니다 (unsigned int) , 되기 전에 으로 더 을 승격한 경우 후속 (컴파일러에 될 수 .) 에 , C 준에 따르면, 모든 산술은 산술 연산이 수행되기 전에 관련된 가장 넓은 유형으로 더 좁은 정수형을 승격(변환)하고, 필요한 경우에는 그 후에 역변환을 해야 합니다. (물론 이것은 충분히 지능적인 컴파일러에 의해 최적화될 수 있습니다.왜 위험을 감수해야 합니까?)
주석 추적을 보면 RISC 프로세서는 32x32->32 비트 곱셈과 16x16->32 비트 곱셈만 제공하는 것으로 보입니다.곱하기 32x-32->64비기 aMULHI64비트 제품의 상위 32비트를 반환하는 명령은 제공되지 않습니다.
반복에 으로 -한 하는 처럼 이며 는 이 를 하기 에 일 으로 이 일 에 하기 를 는 은 MULHI중간 고정점 산술에 대한 명령 또는 확장 곱셈.
아래의 C99 코드는 16x16->32비트 곱셈만 필요로 하지만 다소 선형적으로 수렴하여 최대 6번의 반복을 필요로 하는 다른 반복 접근 방식을 사용합니다.이 접근 방식은 다음을 요구합니다.CLZ반복에 대한 시작 추측을 신속하게 결정할 수 있는 기능입니다.질문자는 코멘트에서 사용된 RISC 프로세서가 CLZ 기능을 제공하지 않는다고 말했습니다.따라서 CLZ에 대한 에뮬레이션이 필요하며, 에뮬레이션을 통해 스토리지 및 명령어 수가 증가하므로 이러한 접근 방식은 경쟁력이 떨어질 수 있습니다.가장 작은 승수를 가진 deBruijn 룩업 테이블을 결정하기 위해 단순한 검색을 수행했습니다.
결과에 합니다. 은 에 를 합니다 합니다 를 ,(int)sqrt(x) 정수 특성 에 항상 편에 . , 의 으로 에 에 의 최종 결과에 도달하기 위해 결과의 제곱이 원래 인수보다 작거나 같을 때까지 조건부로 결과를 감소시킵니다.
.volatile코드의 한정자는 기능에 영향을 주지 않고 모든 명명된 변수를 실제로 16비트 데이터로 할당할 수 있음을 확인하는 역할만 합니다.이것이 어떤 이점을 제공하는지는 모르겠지만, OP가 특별히 자신들의 코드에 16비트 변수를 사용했다는 것을 알게 되었습니다.volatile제거해야 합니다.
대부분의 프로세서의 경우, 끝 부분의 수정 단계에 분기가 포함되지 않아야 합니다.ㅇy*y뺄수다다수에서 뺄 수 .x 차입월는),그음),여입)으로y이월(또는 차입)을 통한 감산으로 보정됩니다. 각 는 순서의가야다는다야ae가o서는edph의eMUL,SUBcc,SUBC.
루프에 의한 반복의 구현은 상당한 오버헤드를 초래하기 때문에, 저는 루프를 완전히 언롤하되, 두 개의 얼리 아웃 체크를 제공하기로 결정했습니다.수동으로 연산을 계산하면 가장 빠른 경우 46개, 평균 경우 54개, 최악의 경우 60개의 연산이 계산됩니다.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
static const uint8_t clz_tab[32] = {
31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1,
23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0};
uint8_t clz (uint32_t a)
{
a |= a >> 16;
a |= a >> 8;
a |= a >> 4;
a |= a >> 2;
a |= a >> 1;
return clz_tab [0x07c4acdd * a >> 27];
}
/* 16 x 16 -> 32 bit unsigned multiplication; should be single instruction */
uint32_t umul16w (uint16_t a, uint16_t b)
{
return (uint32_t)a * b;
}
/* Reza Hashemian, "Square Rooting Algorithms for Integer and Floating-Point
Numbers", IEEE Transactions on Computers, Vol. 39, No. 8, Aug. 1990, p. 1025
*/
uint16_t isqrt (uint32_t x)
{
volatile uint16_t y, z, lsb, mpo, mmo, lz, t;
if (x == 0) return x; // early out, code below can't handle zero
lz = clz (x); // #leading zeros, 32-lz = #bits of argument
lsb = lz & 1;
mpo = 17 - (lz >> 1); // m+1, result has roughly half the #bits of argument
mmo = mpo - 2; // m-1
t = 1 << mmo; // power of two for two's complement of initial guess
y = t - (x >> (mpo - lsb)); // initial guess for sqrt
t = t + t; // power of two for two's complement of result
z = y;
y = (umul16w (y, y) >> mpo) + z;
y = (umul16w (y, y) >> mpo) + z;
if (x >= 0x40400) {
y = (umul16w (y, y) >> mpo) + z;
y = (umul16w (y, y) >> mpo) + z;
if (x >= 0x1002000) {
y = (umul16w (y, y) >> mpo) + z;
y = (umul16w (y, y) >> mpo) + z;
}
}
y = t - y; // raw result is 2's complement of iterated solution
y = y - umul16w (lsb, (umul16w (y, 19195) >> 16)); // mult. by sqrt(0.5)
if ((int32_t)(x - umul16w (y, y)) < 0) y--; // iteration may overestimate
if ((int32_t)(x - umul16w (y, y)) < 0) y--; // result, adjust downward if
if ((int32_t)(x - umul16w (y, y)) < 0) y--; // necessary
return y; // (int)sqrt(x)
}
int main (void)
{
uint32_t x = 0;
uint16_t res, ref;
do {
ref = (uint16_t)sqrt((double)x);
res = isqrt (x);
if (res != ref) {
printf ("!!!! x=%08x res=%08x ref=%08x\n", x, res, ref);
return EXIT_FAILURE;
}
x++;
} while (x);
return EXIT_SUCCESS;
}
또 다른 가능성은 뉴턴 반복을 분할의 높은 비용에도 불구하고 제곱근에 사용하는 것입니다.작은 입력의 경우 한 번의 반복만 필요합니다.,록지만을다로행가eensrfh6n 16 cycle 을 기준으로 합니다.DIV ㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇ32/16->16오버플로우를 방지하기 위해 추가적인 보호 코드가 필요한 비트 분할, 16비트에 맞지 않는 몫으로 정의됩니다.나는 이 가정을 바탕으로 내 코드에 적절한 안전장치를 추가했습니다.
뉴턴 반복은 적용될 때마다 좋은 비트의 수를 두 배로 증가시키기 때문에, 우리는 인수의 5개의 선행 비트를 기반으로 한 표에서 쉽게 가져올 수 있는 낮은 정밀도의 초기 추측만 필요합니다.이것들을 잡기 위해, 우리는 먼저 논법을 2.30의 추가적인 암묵적 스케일 팩터와 함께 2.30 고정 포인트32-(lz & ~1) 형식으로 정규화합니다.lz는 인수에서 선행하는 0의 개수입니다.이전 접근 방식에서와 같이 반복이 항상 정확한 결과를 제공하는 것은 아니므로 예비 결과가 너무 클 경우 수정을 적용해야 합니다.빠른 경로의 경우 49 사이클, 느린 경로의 경우 70 사이클을 계산합니다(평균 60 사이클).
static const uint16_t sqrt_tab[32] =
{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x85ff, 0x8cff, 0x94ff, 0x9aff, 0xa1ff, 0xa7ff, 0xadff, 0xb3ff,
0xb9ff, 0xbeff, 0xc4ff, 0xc9ff, 0xceff, 0xd3ff, 0xd8ff, 0xdcff,
0xe1ff, 0xe6ff, 0xeaff, 0xeeff, 0xf3ff, 0xf7ff, 0xfbff, 0xffff
};
/* 32/16->16 bit division. Note: Will overflow if x[31:16] >= y */
uint16_t udiv_32_16 (uint32_t x, uint16_t y)
{
uint16_t r = x / y;
return r;
}
/* table lookup for initial guess followed by division-based Newton iteration*/
uint16_t isqrt (uint32_t x)
{
volatile uint16_t q, lz, y, i, xh;
if (x == 0) return x; // early out, code below can't handle zero
// initial guess based on leading 5 bits of argument normalized to 2.30
lz = clz (x);
i = ((x << (lz & ~1)) >> 27);
y = sqrt_tab[i] >> (lz >> 1);
xh = (x >> 16); // needed for overflow check on division
// first Newton iteration, guard against overflow in division
q = 0xffff;
if (xh < y) q = udiv_32_16 (x, y);
y = (q + y) >> 1;
if (lz < 10) {
// second Newton iteration, guard against overflow in division
q = 0xffff;
if (xh < y) q = udiv_32_16 (x, y);
y = (q + y) >> 1;
}
if (umul16w (y, y) > x) y--; // adjust quotient if too large
return y; // (int)sqrt(x)
}
어떻게 하면 효율적인 알고리즘으로 바꿀 수 있을지 모르겠지만, 제가 80년대에 이것을 조사했을 때 흥미로운 패턴이 나타났습니다.제곱근을 반올림할 때 앞의 정수(0 이후)보다 해당 제곱근을 갖는 정수가 두 개 더 있습니다.
따라서 1개의 숫자(0)는 0의 제곱근, 2개의 제곱근은 1(1, 2), 4개의 제곱근은 2(3, 4, 5, 6) 등입니다.아마도 유용한 답은 아니지만 그래도 흥미롭습니다.
다음은 기술 @j_random_hacker의 덜 증분 버전입니다.적어도 하나의 프로세서에서는 2년 전에 이 문제를 다루었을 때 조금 더 빨랐습니다.나는 이유를 모르겠다.
// assumes unsigned is 32 bits
unsigned isqrt1(unsigned x) {
unsigned r = 0, r2 = 0;
for (int p = 15; p >= 0; --p) {
unsigned tr2 = r2 + (r << (p + 1)) + (1u << (p + p));
if (tr2 <= x) {
r2 = tr2;
r |= (1u << p);
}
}
return r;
}
/*
gcc 6.3 -O2
isqrt(unsigned int):
mov esi, 15
xor r9d, r9d
xor eax, eax
mov r8d, 1
.L3:
lea ecx, [rsi+1]
mov edx, eax
mov r10d, r8d
sal edx, cl
lea ecx, [rsi+rsi]
sal r10d, cl
add edx, r10d
add edx, r9d
cmp edx, edi
ja .L2
mov r11d, r8d
mov ecx, esi
mov r9d, edx
sal r11d, cl
or eax, r11d
.L2:
sub esi, 1
cmp esi, -1
jne .L3
rep ret
*/
gcc 9 x86 최적화를 설정하면 루프가 완전히 풀리고 상수가 접힙니다.그 결과는 여전히 약 100개의 지시사항에 불과합니다.
언급URL : https://stackoverflow.com/questions/31117497/fastest-integer-square-root-in-the-least-amount-of-instructions
'programing' 카테고리의 다른 글
| 모든 부모가 있는 트리에 있는 노드의 mysql에서 부모/자녀 관계의 전체 트리 가져오기 (0) | 2023.09.12 |
|---|---|
| 스프링 빈즈 - 컨스트럭터 arg로 널 배선하는 방법? (0) | 2023.09.12 |
| Mapstruct - Generated Mapper 클래스에서 스프링 종속성을 주입하는 방법 (0) | 2023.09.12 |
| ajax 시간 초과 콜백 함수 (0) | 2023.09.12 |
| git에서 꺼내는 동안 오류 발생 - 리포지토리 데이터베이스 .git/objects에 개체를 추가할 수 있는 권한이 부족합니다. (0) | 2023.09.12 |