MASM превосходит неоптимизированный .cpp, но не неоптимизированный .c, используя VSC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 MASM превосходит неоптимизированный .cpp, но не неоптимизированный .c, используя VS

Сообщение Anonymous »

У меня есть очень простая функция, которая преобразует вектор (float*) с использованием основной матрицы строк (float**):

Код: Выделить всё

int vector_by_matrix(float** m, float* v, float* out, int size)
{
int i, j;
float temp;

if (!m || !v || !out) return -1;

for (i = 0; i < size; i++)
{
temp = 0;

for (j = 0; j < size; j++)
{
temp += m[i][j] * v[j];
}

//out[i] = temp * v[i]; MISTAKE DURING COPYING - SHOULD'VE BEEN...
out[i] = temp;``
}

return 0;
}
Изначально код компилировался как C++ (x64) с использованием компилятора C++ Visual Studio (2013); и без оптимизации работал довольно медленно (функция вызывалась сотни/тысячи раз во время выполнения, а размер системы обычно большой, c. size = 10000). При высокой оптимизации (O2) и режиме с плавающей запятой на быстром прирост производительности был огромным (x20). Однако я решил преобразовать файл в исходный файл .c и снова скомпилировать его как C с помощью VS — в любом случае это был простой процедурный код. Производительность снова улучшилась (по сравнению с оптимизированной компиляцией C++) с оптимизацией или без нее. На самом деле настройки оптимизации мало повлияли на производительность.

Я не понимаю, почему код на C всегда быстрее (оптимизированный/неоптимизированный). Я дизассемблировал вывод компилятора C(/C++), и он выглядит ужасно — изначально я написал ту же функцию в MASM, и она составляла примерно пятую часть кода, но не могла конкурировать по скорости. Всегда ли VS оптимизирует скомпилированный код C? Судя по дизассемблированному коду, это определенно похоже, но я не уверен. Мой код MASM, если поможет:

Код: Выделить всё

 mul_vector_by_martix proc

mov r10, r9

sub rsp, 8

mov qword ptr[rsp], r11

LI:
MOV rbx, qword ptr[r10*8+rcx[0]-8]

XORPS xmm0, xmm0

mov r11, r9

LJ:

MOVSS xmm1, dword ptr[r11*4+rbx[0]-4]
MULSS xmm1, dword ptr[r11*4+rdx[0]-4]
ADDSS xmm0, xmm1

sub r11, 1

jnz LJ

MOVSS dword ptr[r10*4+r8[0]-4], xmm0

sub r10, 1
jnz LI

mov r11, qword ptr[rsp]

add rsp, 8

ret

mul_vector_by_martix endp
Дизассемблированный код приводить не буду — вопрос достаточно длинный ;)

Заранее спасибо за любая помощь.

Обновление

Сегодня я решил еще раз разобраться в этом. Я реализовал упакованные инструкции (текущая реализация работает только там, где размер системы кратен 4, иначе вы, скорее всего, получите сбой):

Код: Выделить всё

mul_opt_vector_by_martix proc

sub rsp, 8
mov qword ptr[rsp], r12
sub rsp, 8
mov qword ptr[rsp], r13

; copy rdx for arithmetic operations
mov r10, rdx

; init static global
mov r12, LSTEP

cmp VSIZE, r9
je LOOPS

; get sizeof(vector)
mov rax, 4
mul r9
mov r12, rax

; get the number of steps in inner loop
mov r11, 16
mov rax, r12
div r11

mov r11, rax

mov r12, r11

mov rax, 16
mul r12
mov r12, rax
sub r12, 16

mov VSIZE, r9
mov LSTEP, r12

LOOPS:

LI:

MOV rbx, qword ptr[r9*8+rcx[0]-8]

XORPS xmm0, xmm0

mov r13, r12

LJ:

MOVAPS xmm1, xmmword ptr[r13+rbx[0]]
MULPS xmm1, xmmword ptr[r13+r10[0]]

; add the packed single floating point numbers together
MOVHLPS xmm2, xmm1
ADDPS xmm2, xmm1
MOVAPS xmm1, xmm2
SHUFPS xmm2, xmm2, 1 ; imm8 = 00 00 00 01
ADDSS xmm2, xmm1
ADDSS xmm0, xmm2

sub r13, 16

cmp r13, 0
JGE LJ

MOVSS dword ptr[r9*4+r8[0]-4], xmm0

sub r9, 1
jnz LI

mov r13, qword ptr[rsp]
add rsp, 8
mov r12, qword ptr[rsp]
add rsp, 8

ret

mul_opt_vector_by_martix endp
Это улучшает ситуацию примерно на 20-30%, но опять же не может конкурировать с неоптимизированным скомпилированным кодом C. Дизассемблированный код внутреннего цикла:

Код: Выделить всё

                sum += v[j] * m[i][j];
movsxd      rax,r8d
add         rdx,8
movups      xmm0,xmmword ptr [rbx+rax*4]
movups      xmm1,xmmword ptr [r10+rax*4]
lea         eax,[r8+4]
movsxd      rcx,eax
add         r8d,8
mulps       xmm1,xmm0
movups      xmm0,xmmword ptr [rbx+rcx*4]
addps       xmm2,xmm1
movups      xmm1,xmmword ptr [r10+rcx*4]
mulps       xmm1,xmm0
addps       xmm3,xmm1
cmp         r8d,r9d
jl          vector_by_matrix+90h (07FEDD321440h)
addps       xmm2,xmm3
movaps      xmm1,xmm2
movhlps     xmm1,xmm2
addps       xmm1,xmm2
movaps      xmm0,xmm1
shufps      xmm0,xmm1,0F5h
addss       xmm1,xmm0
На этом этапе я должен признать, что не вижу, в чем здесь выгода. Я не удосужился перестроить код на C++, чтобы проверить, отличается ли сборка, но подозреваю, что в неоптимизированном режиме C++ просто не поддается быстрому кодированию, как C с компилятором VS. Возможно, точка зрения Frankie_C уместна. Однако беспокоит то, что если компилятор делает что-то, чего не должен делать, я не вижу в этом ничего плохого; по моему опыту, любая наполовину приличная рукописная сборка превзойдет по производительности неоптимизированный C, но не здесь, с этим компилятором. Операции с плавающей запятой требуют строгого контроля по вопросам точности, иначе результаты могут различаться от одной машины к другой, а методы, которым необходимо сходиться, могут даже дать сбой на одной машине, но не на другой из-за нестабильности.

Обновить 2 ============================================== ===================

Кажется, все прошло очень тихо, но я подумал, что позволю вы все знаете, есть ли у меня улучшения. Что ж, я могу соответствовать компилятору, переставив некоторые операции в циклах, как показано в последнем обновлении. Было совершенно очевидно просто переместить упакованное перетасовывание и сложение за пределы внутреннего цикла. Опять же, из-за неявного размера «векторизации», размер системы должен быть кратен 4 (в противном случае произойдет сбой).

Код: Выделить всё

LOOPS:

LI:

MOV rbx, qword ptr[r9*8+rcx[0]-8]

XORPS xmm0, xmm0

mov r13, r12

LJ:

MOVAPS xmm1, xmmword ptr[r13+rbx[0]]
MULPS xmm1, xmmword ptr[r13+r10[0]]

; just add and accrue
ADDPS xmm0, xmm1

sub r13, 16

cmp r13, 0
jge LJ

;------------ moved this block to the outside --------------;

; add the packed single floating point numbers together
MOVHLPS xmm1, xmm0
ADDPS xmm1, xmm0
MOVAPS xmm0, xmm1
SHUFPS xmm1, xmm1, 1 ; imm8 = 00 00 00 01
ADDSS xmm0, xmm1

;--------------------end block---------------------------

MOVSS dword ptr[r9*4+r8[0]-4], xmm0

sub r9, 1
jnz LI
Все еще не могу превзойти компилятор, но уже очень близок к тому, чтобы сравняться с ним. Я полагаю, что вывод таков: очень сложно превзойти компилятор VS, даже когда дело касается неоптимизированного C - у меня нет опыта работы с (неоптимизированным кодом) другими компиляторами, такими как gcc. Я могу превзойти компилятор, развернув циклы с помощью инструкций SIMD с большим количеством регистраторов xmm. Я могу предоставить это по запросу, но это, вероятно, самоочевидно.

Подробнее здесь: https://stackoverflow.com/questions/348 ... c-using-vs
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»