Суммируйте числа в буфере, используя встроенную сборку в C++
Я новичок в программировании на ассемблере и столкнулся с проблемой, которая может быть очевидна для опытных пользователей на ассемблере. У меня есть 100-байтовый буфер, и мне нужно найти сумму каждого n-го байта для n = 1 до 5 и сохранить результат в массиве из 5 целых чисел. Мне нужно использовать встроенную сборку в моем коде C++, чтобы сделать это. Я написал следующий код:
void main()
{
char *buffer = new char[100];
int *result = new int[5]();
for (int i = 0; i < 100; i++)
buffer[i] = i;
char *start = buffer;
for (int k = 0; k < 20; k++)
{
for (int j = 0; j < 5; j++)
{
__asm
{
mov eax, start[j]
mov ebx, result[j]
add eax, ebx
mov result[j], eax
}
}
start += 5;
}
}
Таким образом, в конце, результат [0] должен иметь сумму в буфере [0], буфере [5], буфере [10] ...., результат [1] будет иметь сумму буфера [1], буфера [6], буфер [11] .... и так далее. Я получаю ошибку Access Violation в самой первой инструкции по сборке (mov eax, start[j]). Может ли кто-нибудь помочь мне узнать, какую ошибку я совершил? Также было бы здорово, если бы кто-нибудь помог мне написать весь цикл и часть суммирования на ассемблере.
1 ответ
Очевидно, я не знаю ваших реальных намерений, но я ставлю под сомнение предположение, что "реальный сценарий, в котором я хочу использовать эту концепцию, может привести к некоторому преимуществу".
Не может быть на 100% точным сказать, что люди больше не могут писать эффективный ассемблер для i386, но это почти так. Если вы знакомы с конвейерной обработкой и выполнением не по порядку, вы уже понимаете, почему это так. Если вы не знакомы с ними, вы уже говорите, что не знаете, как написать эффективный ассемблер.
Это не значит, что вы не должны смотреть на ассемблер для горячих точек вашей программы. Но вам следует написать наиболее эффективный код на языке c, который вы можете, и сравнить его, прежде чем пытаться понять, сможете ли вы написать что-то лучшее в asm. Не удивляйся, если не сможешь.
- Помните, что только потому, что какая-то крошечная подпрограмма работает лучше в крошечной тестовой программе, это не гарантирует, что она будет работать, если она будет включена обратно в исходную программу.
- Или что он будет работать лучше на всех процессорах.
- Или в новых версиях компилятора.
- И, конечно, использование asm означает, что для перехода на новые платформы (например, x64) потребуется переписать asm, что заставит людей, выполняющих работу, проклинать ваше имя.
Тем не менее, вы можете попробовать сравнить что-то вроде этого. Я думаю, что это будет лучше, но это всего лишь предположение.
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
#define MAXTOT 5
typedef unsigned char BYTE;
int main()
{
BYTE *buffer = (BYTE *)malloc(MAXSIZE);
const BYTE *start = buffer;
unsigned int t0, t1, t2, t3, t4;
for (int i = 0; i < MAXSIZE; i++)
buffer[i] = i;
t0 = 0;
t1 = 0;
t2 = 0;
t3 = 0;
t4 = 0;
for (int j=0; j < (MAXSIZE / MAXTOT); j++)
{
t0 += start[0];
t1 += start[1];
t2 += start[2];
t3 += start[3];
t4 += start[4];
start += MAXTOT;
}
printf("%u %u %u %u %u\n", t0, t1, t2, t3, t4);
free(buffer);
return 0;
}
Цикл выглядит так в asm (используя gcc -O2):
L3:
movzbl (%edx), %edi
addl $5, %edx
addl %edi, 44(%esp)
movzbl -4(%edx), %edi
addl %edi, %ebx
movzbl -3(%edx), %edi
addl %edi, %eax
movzbl -2(%edx), %edi
addl %edi, %ecx
movzbl -1(%edx), %edi
addl %edi, %esi
cmpl 40(%esp), %edx
jne L3
Это сохраняет как можно больше "результатов" в регистрах на время вычислений (вместо того, чтобы постоянно читать / записывать их все в память, как ваш существующий код). Меньшее количество циклов также означает меньшее количество команд cmp, и это делает только один проход через буфер вместо 5. Компиляция для x64 (намного проще теперь, когда нет asm) дает еще лучший код, поскольку доступно больше регистров.
Очевидно, что это развалится, если MAXTOT становится больше. Но я могу только прокомментировать код, который я вижу, и 5 - это то, что вы использовали.
FWIW.