LINUX.ORG.RU

inline asm в GCC (i686)... оч.косвенная адресация..


0

0

весёлого времени сУток!

дано: 1)регистр (пусть ebx) содержит смещение в массиве. 2)указатель на массив - в стековой переменной (напр. buffer) 3)С-код:

a=buffer[b];

вопрос: как записать в gcc одну (и только одну!!!) команду, чтоб доступисться к содержимому buffer по смещению offset?

я понимаю, что в конце концов это будет выглядеть в листинге примерно так:

movzbl <смещ.buffer.относит.esp>(%esp,%ebx), %eax
но как добиться этого?

а

__asm__ __volatile__( "movzbl       %0(%%ebx), %%eax":: "m"(buffer));
приводит к бреду типа (в листинге):
movzbl   <смещ.buffer.относит.esp>(%esp)(%ebx), %eax"
т.е. 2 регистра, и каждый в своих скобках!!!

что делать? не вычислять же смещение <смещ.buffer.относит.esp>!!! как потом сопровождать?! (да и вообще - что за бред это в gcc. В VS указывается смещение относительно ebp. на него хоть можно положиться (в разумных прогах)).

а почему вся затея с inline asm? потому, что gcc не оптимизирует код (или я не знаю еще какие опции для этого). на одной машине код небольошого цикла после gcc(v.4.xx) выпоняется в 1.5 раза медленнее, чем после VSC!!! а судя по коду, gcc вообще не знает , что такое конвейр cpu!

а для с-кода, описанного выше, gcc генерирует аж 2(!!!) asm-команды, в то время, как VSC обходится одной... для очистки конвейра заранее на случай непредсказанного перехода gcc не предпрринимает усилий... и тд

подскажите, пож-ста, как записать эту команду!


Вот такой исходник:

#include <stdlib.h>
#include <time.h>

#define SZ 128

int main()
{
	int array[SZ], idx;

	srand((unsigned int) time(NULL));
	for (int i =0; i < SZ; ++i)
		array[i] = rand();
	idx = rand() % SZ; /* !!! don't do that in real code =) */
	return array[idx];
}

В результате (gcc -O2) получаем

   1              		.file	"test.c"
   2              		.text
   3              		.p2align 4,,15
   4              	.globl main
   6              	main:
   7 0000 55       		pushl	%ebp
   8 0001 89E5     		movl	%esp, %ebp
   9 0003 83E4F0   		andl	$-16, %esp
  10 0006 56       		pushl	%esi
  11 0007 53       		pushl	%ebx
  12 0008 31DB     		xorl	%ebx, %ebx
  13 000a 81EC1802 		subl	$536, %esp
  13      0000
  14 0010 C7042400 		movl	$0, (%esp)
  14      000000
  15 0017 8D742410 		leal	16(%esp), %esi
  16 001b E8FCFFFF 		call	time
  16      FF
  17 0020 890424   		movl	%eax, (%esp)
  18 0023 E8FCFFFF 		call	srand
  18      FF
  19 0028 908DB426 		.p2align 4,,10
  19      00000000 
  20              		.p2align 3
  21              	.L2:
  22 0030 E8FCFFFF 		call	rand
  22      FF
  23 0035 89049E   		movl	%eax, (%esi,%ebx,4)
  24 0038 43       		incl	%ebx
  25 0039 81FB8000 		cmpl	$128, %ebx
  25      0000
  26 003f 75EF     		jne	.L2
  27 0041 E8FCFFFF 		call	rand
  27      FF
  28 0046 89C2     		movl	%eax, %edx
  29 0048 C1FA1F   		sarl	$31, %edx
  30 004b C1EA19   		shrl	$25, %edx
  31 004e 01D0     		addl	%edx, %eax
  32 0050 83E07F   		andl	$127, %eax
  33 0053 29D0     		subl	%edx, %eax
  34 0055 8B448410 		movl	16(%esp,%eax,4), %eax
  35 0059 81C41802 		addl	$536, %esp
  35      0000
  36 005f 5B       		popl	%ebx
  37 0060 5E       		popl	%esi
  38 0061 C9       		leave
  39 0062 C3       		ret
  41              		.ident	"GCC: (Gentoo 4.4.0_alpha20090407) 4.4.0-alpha20090407  (prerelease)"
  42              		.section	.note.GNU-stack,"",@progbits

Если я правильно понял этот ассемблерный листинг, то доступ к массиву и в случае чтения и в случае записи производится одной командой.

Ты лучше дай реальный кусок кода и скажи какой версией компилятора и с какими опциями компилировал.

Deleted
()

>т.е. 2 регистра, и каждый в своих скобках!!!
это пиз..ц подумал штирлиц. 
>gcc генерирует аж 2(!!!) asm-команды
не верю
>В VS указывается смещение относительно ebp. на него хоть можно положиться (в разумных прогах))
песец - давно ли компилятор должен отчитываться относительно каких регистров он ищет смещение

2миронов_иван 
открой для себя objdump -S а то в твоих листингах кроме компилятора никто не разберется

bash-3.2# cat 15.c
#include <stdlib.h>
#include <time.h>

#define SZ 128

int main()
{
    int array[SZ], idx;

    srand((unsigned int) time(NULL));
    for (int i =0; i < SZ; ++i) {
	array[i] = rand();
	idx = array[i];
    }
    return 0;
}

bash-3.2# gcc -g -std=c99 15.c 

080483e4 <main>:
#include <time.h>

#define SZ 128

int main()
{
 80483e4:	8d 4c 24 04          	lea    0x4(%esp),%ecx
 80483e8:	83 e4 f0             	and    $0xfffffff0,%esp
 80483eb:	ff 71 fc             	pushl  -0x4(%ecx)
 80483ee:	55                   	push   %ebp
 80483ef:	89 e5                	mov    %esp,%ebp
 80483f1:	53                   	push   %ebx
 80483f2:	51                   	push   %ecx
 80483f3:	81 ec 20 02 00 00    	sub    $0x220,%esp
    int array[SZ], idx;

    srand((unsigned int) time(NULL));
 80483f9:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048400:	e8 03 ff ff ff       	call   8048308 <time@plt>
 8048405:	89 04 24             	mov    %eax,(%esp)
 8048408:	e8 cb fe ff ff       	call   80482d8 <srand@plt>
    for (int i =0; i < SZ; ++i) {
 804840d:	c7 45 f4 00 00 00 00 	movl   $0x0,-0xc(%ebp)
 8048414:	eb 20                	jmp    8048436 <main+0x52>
	array[i] = rand();
 8048416:	8b 5d f4             	mov    -0xc(%ebp),%ebx
 8048419:	e8 fa fe ff ff       	call   8048318 <rand@plt>
 804841e:	89 84 9d f0 fd ff ff 	mov    %eax,-0x210(%ebp,%ebx,4)
	idx = array[i];
 8048425:	8b 45 f4             	mov    -0xc(%ebp),%eax
 8048428:	8b 84 85 f0 fd ff ff 	mov    -0x210(%ebp,%eax,4),%eax
 804842f:	89 45 f0             	mov    %eax,-0x10(%ebp)
int main()
{
    int array[SZ], idx;

    srand((unsigned int) time(NULL));
    for (int i =0; i < SZ; ++i) {
 8048432:	83 45 f4 01          	addl   $0x1,-0xc(%ebp)
 8048436:	83 7d f4 7f          	cmpl   $0x7f,-0xc(%ebp)
 804843a:	7e da                	jle    8048416 <main+0x32>
	array[i] = rand();
	idx = array[i];
    }
    return 0;
 804843c:	b8 00 00 00 00       	mov    $0x0,%eax
}
 8048441:	81 c4 20 02 00 00    	add    $0x220,%esp
 8048447:	59                   	pop    %ecx
 8048448:	5b                   	pop    %ebx
 8048449:	5d                   	pop    %ebp
 804844a:	8d 61 fc             	lea    -0x4(%ecx),%esp
 804844d:	c3                   	ret    

gcc version 4.3.3

monika
()
Ответ на: комментарий от Deleted

спасибо за ответ!
я и сам вижу, что иной раз gcc генерит такой код. но :
1) это происходит не каждый раз
2) gcc неоптимально использует реистры. вместо того, чтобы самые используемые переменные определить в регистрах навечно, gcc предлагает доставать их каждый раз из стека!!!

читайте в моем пред.посте
(http://www.linux.org.ru/view-message.jsp?msgid=3637010&lastmod=1239962380560)
какая разница в производительности!!! это же бред!!
поэтому мне и нужно самому переписать самое узкое место в коде, задействовав все регистры и оптимизировав использование кэша проца.

версия gcc:
gcc version 4.2.1 20070719  [FreeBSD]
опции компиляции:
-fomit-frame-pointer -O3 -march=i686



короткий пример С-кода:

#define get_offset(p,i) (p)[(i)]
 hash1  += get_offset(poffset,*(ptext-1)); \
 hash1 <<= _NBITSINSHIFT; \
 hash1  += get_offset(poffset,*(ptext)); \
 hash1 &= ((1<<_HASH_BITS) -1);
 if ( shift > 0 )
 {
    ptext += shift;
 }
 else
 {
    ...
 }

и вот что сгенерировал MS-VSC:
  ; esi не читается и не пишется в 
  movzx       eax,byte ptr [esi] 
  movzx       ecx,byte ptr [esi-1] 
  ; [ebp+128h] - указатель
  movzx       eax,byte ptr [eax+ebp+128h] 
  movzx       ecx,byte ptr [ecx+ebp+128h] 
  shl         eax,8 
  add         eax,ecx 
  test        edx,edx 
  je          L_st
  movzx       ecx,byte ptr [esi-2] 
  movzx       ecx,byte ptr [ecx+ebp+128h] 
  shl         eax,8 
  add         eax,ecx 
L_st:
	;st = pSTable[ hash1 ];
  mov         ecx,dword ptr [esp+1Ch] 
  movzx       ecx,byte ptr [eax+ecx] 
  test        ecx,ecx 
  jbe         ..(004029E4)
  ;{
  ; ptext += shift;
    ; esi просто изменяется
    add         esi,ecx 
  ;}
  ;else
004029E4
  ...

а вот, что за [eqy. сгенерировал gcc:
	; достает переменную из стека, хотя она должна быть в регистре заранее
	movl	-28(%ebp), %edx ; переменная ptext
	; достает переменную из стека, хотя она должна быть в регистре заранее
	movl	-32(%ebp), %ecx ; переменная poffset
	movzbl	(%edx), %eax
	movzbl	-1(%edx), %edx
	movzbl	(%eax,%ecx), %eax
	movzbl	(%edx,%ecx), %edx
	sall	$8, %eax
	cmpl	$2, -64(%ebp)
	leal	(%edx,%eax), %ecx
	jbe	.L16 
	; ОПЯТЬ достает ТУ ЖЕ переменную из стека! gcc просто охetk!
	movl	-28(%ebp), %edx ; переменная ptext
	movl	%ecx, %eax
	; и ОПЯТЬ достает ТУ ЖЕ переменную из стека! gcc просто охetk! 
	movl	-32(%ebp), %ecx ; переменная poffset
	sall	$8, %eax
	movzbl	-2(%esi), %edx
	movzbl	(%edx,%ecx), %edx
	leal	(%eax,%edx), %ecx
.L16:
	; достает переменную из стека, хотя она должна быть в регистре заранее
	movl	-48(%ebp), %esi
	movzbl	(%esi,%ecx), %eax
	testb	%al, %al
	jne	.L42
 
т.о. в коде, который повторяется миллионы раз, появилось несколько лишниш инструкций проца с обращением к памяти.
правда к кэш-памяти, но это не меняет дела - производительность-то упала в 1.5 раза!!! ведь это не единственное опасное место.

может, gcc надо какие-то еще опции указать?

v40in
() автор топика
Ответ на: комментарий от v40in

> 2) gcc неоптимально использует реистры. вместо того, чтобы самые используемые переменные определить в регистрах навечно, gcc предлагает доставать их каждый раз из стека!!!

Кстати говоря:

A new register allocator has replaced the old one. It is called integrated register allocator (IRA) because coalescing, register live range splitting, and hard register preferencing are done on-the-fly during coloring. It also has better integration with the reload pass. IRA is a regional register allocator which uses modern Chaitin-Briggs coloring instead of Chow's priority coloring used in the old register allocator. More info about IRA internals and options can be found in the GCC manuals. (с) http://gcc.gnu.org/gcc-4.4/changes.html

Может имеет смысл обновиться до самой последней версии? Там кстати добавили ещё несколько вкусностей, связанных с оптимизацией.

По поводу остального: ждём комментариев от более шарящих в этом деле людей. Лично я в сабже плохо разбираюсь...

Deleted
()
Ответ на: комментарий от monika

спасибо за ответ!

> может явно указать класс памяти для этой переменной volatile register ?


не помогло. лишь листинг немного изменился :(

v40in
() автор топика
Ответ на: комментарий от v40in

>> может явно указать класс памяти для этой переменной volatile register ?

>не помогло. лишь листинг немного изменился :(


Интересно девки пляшут - вообщето это должно говорить компилятору что эту переменную мы непременно хотим видеть только на регистрах. Можно кусок кода с местом где объявлена переменная и дизассемблерный листинг - только желательно после objdump -S ?

monika
()
Ответ на: комментарий от v40in

Очень похоже на эффекты aliasing. Вероятно gcc не может быть уверен, что ptext/poffset не меняется. Это бывает когда имеются указатели и преобразование типов указателей. По фрагменту это не ясно - маловат.

См. всякие ключи с alias + нельзя ли использовать "restrict"?

io ★★
()
Ответ на: комментарий от monika

volatile, как правило, приводит к замедлению (дополнительные ограничения на код), а не ускорению кода.

io ★★
()
Ответ на: комментарий от io

>volatile, как правило, приводит к замедлению (дополнительные ограничения на код), а не ускорению кода.

Высказывание из разряда "пальцем в небо" - gcc тут так лихо соптимизировал :) volatile должно указать ему что мы не хотим его дурацких "оптимизаций" и пусть он делает только то что ему сказано - разместит переменную на региистрах, не выкинет эту переменную даже если она не изменяется и тд. Имеет ли volatile побочные эфекты пусть топикстартер проверяет - это сразу на скорости отразится.

monika
()
Ответ на: комментарий от monika

register является чисто рекомендательным, volatile не сделает его обязательным, а вот оптимизацию вырубит.

io ★★
()
Ответ на: комментарий от io

спасибо всем. но можно я напомню вопрос. :)
его суть примерно такая (см.
http://www.linux.org.ru/view-message.jsp?msgid=3637010&lastmod=1239973293...

как методами __inline asm__ записать в C gcc единственную команду цпу, типа
movzbl XX(%reg1,%reg2) %destreg
соответствующую С-коду
a=p[i];
причем смещение (i) уже находится в регистре (и destreg не нужно сохранять в память).
еще раз спасибо

v40in
() автор топика
Ответ на: комментарий от v40in

> соответствующую С-коду  a=p[i]; 

       for (int i =0; i < SZ; ++i)
                array[i] = rand();
 80484b0:	e8 e3 fe ff ff       	call   8048398 <rand@plt>
 80484b5:	89 44 9e fc          	mov    %eax,0xfffffffc(%esi,%ebx,4)
 80484b9:	43                   	inc    %ebx
 80484ba:	81 fb 81 00 00 00    	cmp    $0x81,%ebx
 80484c0:	75 ee                	jne    80484b0 <main+0x40>


Это оно?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от v40in

Что-то у меня с раздачи gcc генерирует 2-регистровое обращение и
без asm и с ним. Вот пара примеров:

main() {
        int array[10];
        int i;
        for(i=0;i<10;i++) {
                int j, z;
                j = i * i;
                //z = array[j];
                asm("movl %1,%0" : "=r" (z) : "m" (array[j]));
                x(z+1);
        }
}

        imull   %ebx, %eax
        incl    %ebx
/APP
        movl -56(%ebp,%eax,4),%eax
/NO_APP
        incl    %eax
        movl    %eax, (%esp)
        call    _x

=========
main() {
        int array[10];
        int i;
        for(i=0;i<10;i++) {
                int j, z;
                j = i * i;
                //array[j] = rand();
                z = rand();
                asm("movl %1,%0" : "=m" (array[j]) : "r" (z));
        }
}

        imull   %esi, %ebx
        incl    %esi
        call    _rand
/APP
        movl %eax,-56(%ebp,%ebx,4)
/NO_APP
        cmpl    $9, %esi

io ★★
()
Ответ на: комментарий от v40in

> спасибо. да. это оно. но надо бы с помощью inline asm

не знаю инлайн асма :-(

а может все же как-то по-другому, например взять старый gcc в котором -O3 не поломано? у меня это сгенерено gcc (GCC) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от io

> asm("movl %1,%0" : "=m" (array[j]) : "r" (z));

спасибо, io! до этого я не додумкался!

А еще я испробовал такой параметр gcc:
-fno-guess-branch-probability

Производительность поднялась на 4%!

Я-то думал, что проц.(по кр.мере из i686) должен заниматься предугадыванием переходов на... А еще и компилятор берет на себя этот труд на...
И тогда при отсутствии -fno-guess-branch-probability,
do {..} while(..)
превращается в
while(..){..} !!!
Апупеть!

v40in
() автор топика
Ответ на: комментарий от monika

> Интересно девки пляшут - вообщето это должно говорить компилятору что эту переменную мы непременно хотим видеть только на регистрах.

Уже давно слово register ничего не меняет

namezys ★★★★
()
Ответ на: комментарий от io

io, а как сделать так, чтобы не надо было в
> asm("movl %1,%0" : "=r" (z) : "m" (array[j]));

указывать j? ведь j у меня уже в регистре. даже сложнее:
j - это значение, вынутое из некого указателя {int* ptr}:
asm("movl %1,%0" : "=r" (z) : "m" (array[*ptr]));
так получается сложне: опять же gcc генерирует перед
> asm("movl %1,%0" : "=r" (z) : "m" (array[j]));

инструкцию для чтения ptr итд.
но ptr - ужЕ в регистре. заранее! он оттуда никуда не дувается.

похоже, придется писать на Asm-e :(

v40in
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.