인라인 어셈블리를 사용하여 컴파일러를 때리는 데 어려움을 겪고 있습니다.인라인 어셈블리에서보다 빨리 구현되는 간단한 C 함수의 예는 무엇입니까?
컴파일러가 실제로 정말 빠르고 간단하게 만드는 데 어려움을 겪고있는 좋은 기능과 비예책의 예제는 무엇입니까? 인라인 어셈블리를 사용하면 상대적으로 간단합니다.
인라인 어셈블리를 사용하여 컴파일러를 때리는 데 어려움을 겪고 있습니다.인라인 어셈블리에서보다 빨리 구현되는 간단한 C 함수의 예는 무엇입니까?
컴파일러가 실제로 정말 빠르고 간단하게 만드는 데 어려움을 겪고있는 좋은 기능과 비예책의 예제는 무엇입니까? 인라인 어셈블리를 사용하면 상대적으로 간단합니다.
아이폰과 어셈블리 코드와 관련이 있기 때문에 아이폰 세계에서 관련이있을만한 예제를 제공 할 것입니다 (일부 sse 또는 x86 asm이 아님). 누군가 실제 응용 프로그램을위한 어셈블리 코드를 작성하기로 결정하면 디지털 신호 처리 또는 이미지 조작이 될 가능성이 높습니다. 예 : RGB 픽셀의 색상 공간 변환, 이미지 인코딩 jpeg/png 형식 또는 사운드를 mp3, amr 또는 g729로 인코딩하여 VoIP 응용 프로그램 용. 사운드 인코딩의 경우에는 컴파일러에서 효율적인 asm 코드로 변환 할 수없는 많은 루틴이 있습니다. C에서 이와 동등한 기능이 없습니다. 사운드 처리에서 일반적으로 사용되는 예 : 포화 된 수학, 곱하기 누적 루틴, 행렬 곱셈 .
포화 된 추가의 예 : 32 비트 부호있는 int의 범위는 0x8000 0000 < = int32 < = 0x7fff ffff입니다. 두 개의 int를 추가하면 결과가 오버플로 될 수 있지만 디지털 신호 처리의 특정 경우에는 받아 들일 수 없습니다. 기본적으로 결과 오버플로 또는 언더 플로우가 포화 된 경우 0x8000 0000 또는 0x7fff ffff를 반환해야합니다. 그것을 확인하는 완전한 함수가 될 것입니다.포화 추가의 최적화 된 버전이 될 수있다 :
int saturated_add(int a, int b) { int result = a + b; if (((a^b) & 0x80000000) == 0) { if ((result^a) & 0x80000000) { result = (a < 0) ? 0x80000000 : 0x7fffffff; } } return result; }
당신은 또한/다른 오버 플로우 또는 당신이 (또한 ASM을 사용하는 데 필요한) 오버 플로우 플래그를 체크 할 수있다 x86에서 여러 확인 할 수 있습니다합니다. iPhone은 dsp asm을 사용하는 armv6 또는 v7 cpu를 사용합니다. 따라서 복수 브런치 (if/else 문) 및 2 32 비트 상수를 사용하는 saturated_add
함수는 하나의 CPU 사이클을 사용하는 하나의 간단한 asm 명령이 될 수 있습니다. 따라서 단순히 asm 명령을 사용하기 위해 saturated_add를 만드는 것은 전체 알고리즘을 2-3 배 빠르게 (크기는 더 작게) 만들 수 있습니다. 여기에 QADD 설명서의 : 종종 긴 루프에서 실행되는 코드의 QADD
다른 예이다
res1 = a + b1*c1; res2 = a + b2*c2; res3 = a + b3*c3;아무것도 여기에 최적화 될 수없는 것 같다,하지만 ARM의 CPU에 특정 DSP 명령어를 사용할 수 간단한 곱셈을하는 것보다 적은 사이클을 잡으십시오! 맞습니다. 구체적인 지침이있는 a + b * c는 단순한 a * b보다 빠르게 실행될 수 있습니다. 이런 종류의 경우 컴파일러는 코드 논리를 이해할 수 없으므로 이러한 dsp 명령어를 직접 사용할 수 없으므로 코드를 최적화하기 위해 수동으로 asm을 작성해야하지만 코드의 일부만 수동으로 작성해야합니다. 최적화. 수동으로 간단한 루프를 작성하기 시작하면 거의 확실하게 컴파일러를 이길 수 없습니다! 웹상에 전나무 필터, amr 인코딩/디코딩 등을 코드하는 인라인 어셈블러에 대한 여러 가지 좋은 논문이 있습니다.
컴파일러를 통해 얻은 가장 좋은 결과는 간단한 memcpy 루틴이었습니다 ... 기본 설정 항목을 많이 건너 뛰었습니다 (예 : 스택 프레임을 많이 필요로하지 않으므로 몇 사이클을 절약 할 수 있습니다.), 그리고 꽤 털이 많은 것을했다.
약 6 년 전, 알려지지 않은 품질의 독점 컴파일러로 이루어졌습니다. 내가 가진 코드를 파헤쳐 GCC와 비교해보아야 할 것이다. 나는 그것이 더 빨라질 수 있다는 것을 모른다. 그러나 나는 그것을 배제하지 않을 것이다.
필자의 memcpy가 C 라이브러리의 평균보다 15 배 더 빠르더라도 필자는 필자가 필요로 할 때를 대비하여 나의 백 포켓에 보관했다. PPC 어셈블리를 가지고 노는 것은 나를위한 장난감이었고, 우리의 어플리케이션에는 속도 향상이 필요하지 않았습니다.
SIMD 작업과 같은 작업을 수행하려는 경우 컴파일러를 능가 할 수 있습니다. 이것은 아키텍처와 명령어 세트에 대한 지식이 필요합니다.
어셈블리를 처리 할 때 아키텍처 및 명령어 세트를 이해하는 중요성을 과소 평가할 수는 없습니다. 저는 일반적으로 asm을 피합니다. 그러나 이론적 인 성능에 대한 아이디어를 가질 수 있도록 아키텍처의 기능을 배우는 것이 중요합니다. – NoMoreZealots
당신이 SIMD 작업 (그것도 자동 벡터화 (autovectorization)이있는 경우!), 당신은 일반적으로 컴파일러의 자동 벡터화 (autovectorization) 능력보다 훨씬 더 수행 SIMD 어셈블리를 쓸 수있는 86 년대의
Here's 아주 기본적인 SSE (한 바람을 피우고 고려하지 않는 경우 SIMD 명령어 세트) 자습서. Visual C++ 인라인 어셈블리 용입니다.
편집 : 다음은 자신을 위해 시도하려는 작은 쌍의 기능입니다. 그것은 n 길이 내적을 계산하는 것입니다. 하나는 SSE 2 명령 인라인 (GCC 인라인 구문)을 사용하고 다른 하나는 매우 기본입니다.
아주 간단합니다. 좋은 컴파일러가 단순한 C 루프를 벡터화 할 수 없다면 매우 놀랍습니다. , 그렇지 않으면 SSE2의 속도가 빨라야합니다. SSE 2 버전은 레지스터를 더 많이 사용하면 더 빠를 수 있지만 매우 약한 SSE 기술을 늘리고 싶지는 않습니다. :)
float dot_asm(float *a, float*b, int n)
{
float ans = 0;
int i;
// I'm not doing checking for size % 8 != 0 arrays.
while(n > 0) {
float tmp[4] __attribute__ ((aligned(16)));
__asm__ __volatile__(
"xorps %%xmm0, %%xmm0\n\t"
"movups (%0), %%xmm1\n\t"
"movups 16(%0), %%xmm2\n\t"
"movups (%1), %%xmm3\n\t"
"movups 16(%1), %%xmm4\n\t"
"add $32,%0\n\t"
"add $32,%1\n\t"
"mulps %%xmm3, %%xmm1\n\t"
"mulps %%xmm4, %%xmm2\n\t"
"addps %%xmm2, %%xmm1\n\t"
"addps %%xmm1, %%xmm0"
:"+r" (a), "+r" (b)
:
:"xmm0", "xmm1", "xmm2", "xmm3", "xmm4");
__asm__ __volatile__(
"movaps %%xmm0, %0"
: "=m" (tmp)
:
:"xmm0", "memory");
for(i = 0; i < 4; i++) {
ans += tmp[i];
}
n -= 8;
}
return ans;
}
float dot_c(float *a, float *b, int n) {
float ans = 0;
int i;
for(i = 0;i < n; i++) {
ans += a[i]*b[i];
}
return ans;
}
SIMD는 분명히 부정 행위를하지 않습니다. 컴파일러가 하드웨어를 따라 잡지 못하는 경우에 대한 명확한 사례를 제공합니다. C는 명령 수준 병렬 처리를 잘 처리하지 못합니다. 어쩌면 여기 저기에 풀려나는 고리가있을 수 있지만 더 진보 된 일과에는 심각한 조정이 필요합니다. – NoMoreZealots
SIMD 명령어를 출력 할 컴파일러가 많이 있습니다. – jrockway
제한된 경우에 해당됩니다. 기본적으로 코드가 일반적인 기법이나 알고리즘으로 작성되는 한. 일단 명령어 세트가 너무 커지면 컴파일러 나 최적화 프로그램을 단순히 복잡성 때문에 쓰기 만하면 많은 명령어를 최적으로 사용할 수 있습니다. 이는 "RISC"프로세서 개념의 기초가 된 부분입니다. 최적화는 체스와 비슷하지만 컴퓨터가 대다수 사람들을 꺾을 수는 있지만 데스크톱보다 그랜드 마스터를이기는 데는 많은 시간이 걸립니다. – NoMoreZealots
당신이 assembly guru을하지 않는 한 컴파일러를 치는 확률이 매우 낮은 있습니다.
상기 링크에서 단편
예를 들어, "% EAX XOR %의 EAX"명령어는 초기 세대 제로 에 레지스터를 설정 빠른 방법 비트 중심이었다 x86의 경우 이지만 대부분의 코드는 컴파일러 및 컴파일러에서 생성됩니다. 은 XOR 명령어를 생성하지 않습니다. 그래서 IA 디자이너, 최대 문자 그대로 "MOVL $ 0 % EAX" 명령을 만드는 조합 디코딩 로직 의 전면 에 자주 발생하는 컴파일러 생성 지침을 이동하기로 결정은 XOR 명령보다 더 빨리 실행됩니다.
저는 어셈블리 전문가가 아니며 컴파일러를 이겼습니다. 나는 거의 어셈블리에 의지하지 않는다.내가해야 할 때 최후의 수단이었다. 이것은 그냥 말하지 않는 것처럼 보입니다. 그리고 그것은 그의 질문을 무시합니다. 그는 그 질문에서 쉽지 않다는 것을 인정한다. – NoMoreZealots
나는 그것이 불가능하다고 말하지 않았다. 명령어 집합을 괴롭히는 경우보다 빠른 코드를 작성하거나 더 적은 수의 명령어로 루틴을 집어 넣으려고 할 수 있습니다. 매우 정교하지 않은 컴파일러가 있거나 컴파일러가 sse, 3dnow 세트를 처리하지 못하는 경우 어셈블리 작성이 일부 루틴을 구현하는 * 적절한 방법 일 수 있습니다. –
당신이 맞다면, 당신이 complier를 때리기의 어떤 희망도 갖고 싶은 경우에 명령 세트를 이해하는 것은 절대 필요합니다. 그러나 훌륭한 컴파일러를 사용하더라도 현대 아키텍처에서 잘 매핑되는 C 구문이없는 명령어를 찾을 수 있습니다. 멀티 코어 패러다임이 표준이되면서 커지는 추상화에는 여전히 "간격"이 있습니다. 오늘날의 전력 소비 및 이동 시장에서 우리는 애플리케이션에서 CPU 코어 속도가 더 빠르다고 가정 할 수 없습니다. 1999 년에 CPU가 1GHz를 기록했고, 새로운 응용 프로그램이 "가장 뜨거웠 던"하드에서 현재 400Mhz로 클록 킹하고 있습니다. – NoMoreZealots
일반적인 "해협 C"구현을 사용하여 간단한 상호 상관 관계를 구현했습니다. 그런 다음 내가 사용할 수있는 타임 슬라이스보다 더 오래 걸렸을 때, 알고리즘의 명시적인 병렬 처리와 프로세서 내장 함수를 사용하여 특정 명령어를 강제로 계산에 사용하도록했습니다. 이 특별한 경우에 대해 계산 시간은 30ms 이상에서 4ms 이상으로 단축되었습니다. 다음 데이터 수집이 발생하기 전에 처리를 완료하기 위해 15ms의 창이있었습니다.
이것은 VLWI 프로세서에서의 SIMD 유형 최적화였습니다. 이 작업은 기본적으로 4 개 정도의 프로세서 내장 함수를 필요로합니다.이 내장 함수는 기본적으로 소스 코드에서 함수 호출의 모양을 나타내는 어셈블리 언어 명령어입니다. 인라인 어셈블리에서도 동일한 작업을 수행 할 수 있지만 구문 및 레지스터 관리는 프로세서 내장 함수로 약간 더 멋지다.
크기가 중요하다면 어셈블러는 왕이 아닙니다. 나는 512 바이트 이하로 전체 화면 텍스트 편집기를 작성한 남자와 학교에 다녔다.
이것은 어셈블러가 합리적인 전형적인 경우입니다. 코드는 C로 작성되었습니다. 잘되었지만 충분히 빠르지는 않습니다. 어셈블러에서 레코딩을하면 작업 속도가 빨라 졌기 때문에 어셈블러에 들러야 할 좋은 이유가되었습니다. –
해트트 C 버전에서 나온 성능에 나는 실망했다. 칩 벤더의 선전은 C 컴파일러가 얼마나 좋은지 자랑했다. 그리고 그들은 가장 최근의 툴체인이 그것을 최적화하는 어떤 더 나은 일도하지 않습니다. 불행히도 VLWI가있는 DSP는 최적화 프로그램을 작성하기 쉽지 않습니다. – NoMoreZealots
단어를 특정 비트 수만큼 회전해야하는 체크섬 알고리즘이 있습니다.
//rotate word n right by b bits
#define ROR16(n,b) (((n)>>(b))|(((n)<<(16-(b)))&0xFFFF))
//... and inside the inner loop:
sum ^= ROR16(val, pos);
으로 VisualStudio의 릴리스 빌드이로 확장 :를 구현하려면,이 매크로를 가지고 (val
이 sum
이 BX에, pos
는 DX에, 도끼에)
mov ecx,10h
sub ecx,edx
mov ebp,eax
shl ebp,cl
mov cx,dx
sar ax,cl
add esi,2
or bp,ax
xor bx,bp
더 많은 효율적인 동등 손으로 생성 된 어셈블리는 다음과 같습니다 제가 파악하지 않은
mov cl,dx
ror ax,cl
xor bx,ax
순수 'C'에서 ror
명령을 방출하는 방법 암호. 그러나 ...
이 글을 쓰면서 컴파일러 내장 함수를 기억해 냈습니다. 나는과 지침의 두 번째 세트를 생성 할 수 있습니다
sum ^= _rotr16(val,pos);
그래서 내 대답은 : 당신은, 순수한 C 컴파일러를 이길 어셈블리를 인라인 의지하기 전에 내장 함수를 확인할 수 있다고 생각하더라도.
좋은 예입니다. – NoMoreZealots
gcc (4.0.1)에서 -O4와 함께 시도했습니다. 32 비트 회전의 경우 ROR 명령어를 출력하지만 16 비트의 경우는 출력하지 않습니다. – finnw
당신을 선택하지는 않겠지 만 최적화를 요청하고 속도를 묻는 질문에 아주 많은 사람들이 있습니다. 요구 사항을 충족하지 못하기 때문에 필요하다고 말하는 고객은 거의 없습니다. 분명히 우리는 "조기 최적화가 모든 악마의 뿌리"라고 생각하지 않았습니다. –
내 질문에 대한 답은 제가 iPhone에서 인라인 어셈블리를 사용하여 주위를 핥고 있었고 블로그 게시물을 작성하려고했습니다. . 하지만 내 인생은 내 컴파일러를 능가 할 수 없었습니다. 그래서 나는 컴파일러가 비효율적 인 코드를 생성하는 알려진 경우가 있는지 궁금해합니다. –
ARM 어셈블리는 "클리너"명령어 세트 중 하나입니다. RISC 프로세서의 철학 중 일부는 컴파일러에서 쉽게 사용하지 않는 지침을 추가하지 않는 것입니다. 특정 ARM 변형의 명령어 세트를보고 명확한 C 변환이없는 opcode를 찾아야합니다. – NoMoreZealots