Комментарии 51
Есть планы написать виртуальную машину для исполнения шейдеров? :-)
Объясните пожалуйста, кто отломал от вашего компилятора циклы?
Если вы так сделали, думая что компилятор — идиот и не умеет разворачивать циклы, а затем реализовывать их в векторных инструкциях в глубоко ошибаетесь.
matrix4x4 matrix4x4::operator*(matrix4x4 const &m) const
{
return matrix4x4{
values[0][0] * m.values[0][0] + values[0][1] * m.values[1][0] + values[0][2] * m.values[2][0] + values[0][3] * m.values[3][0],
values[0][0] * m.values[0][1] + values[0][1] * m.values[1][1] + values[0][2] * m.values[2][1] + values[0][3] * m.values[3][1],
values[0][0] * m.values[0][2] + values[0][1] * m.values[1][2] + values[0][2] * m.values[2][2] + values[0][3] * m.values[3][2],
values[0][0] * m.values[0][3] + values[0][1] * m.values[1][3] + values[0][2] * m.values[2][3] + values[0][3] * m.values[3][3],
values[1][0] * m.values[0][0] + values[1][1] * m.values[1][0] + values[1][2] * m.values[2][0] + values[1][3] * m.values[3][0],
values[1][0] * m.values[0][1] + values[1][1] * m.values[1][1] + values[1][2] * m.values[2][1] + values[1][3] * m.values[3][1],
values[1][0] * m.values[0][2] + values[1][1] * m.values[1][2] + values[1][2] * m.values[2][2] + values[1][3] * m.values[3][2],
values[1][0] * m.values[0][3] + values[1][1] * m.values[1][3] + values[1][2] * m.values[2][3] + values[1][3] * m.values[3][3],
values[2][0] * m.values[0][0] + values[2][1] * m.values[1][0] + values[2][2] * m.values[2][0] + values[2][3] * m.values[3][0],
values[2][0] * m.values[0][1] + values[2][1] * m.values[1][1] + values[2][2] * m.values[2][1] + values[2][3] * m.values[3][1],
values[2][0] * m.values[0][2] + values[2][1] * m.values[1][2] + values[2][2] * m.values[2][2] + values[2][3] * m.values[3][2],
values[2][0] * m.values[0][3] + values[2][1] * m.values[1][3] + values[2][2] * m.values[2][3] + values[2][3] * m.values[3][3],
values[3][0] * m.values[0][0] + values[3][1] * m.values[1][0] + values[3][2] * m.values[2][0] + values[3][3] * m.values[3][0],
values[3][0] * m.values[0][1] + values[3][1] * m.values[1][1] + values[3][2] * m.values[2][1] + values[3][3] * m.values[3][1],
values[3][0] * m.values[0][2] + values[3][1] * m.values[1][2] + values[3][2] * m.values[2][2] + values[3][3] * m.values[3][2],
values[3][0] * m.values[0][3] + values[3][1] * m.values[1][3] + values[3][2] * m.values[2][3] + values[3][3] * m.values[3][3]};
}
Если вы так сделали, думая что компилятор — идиот и не умеет разворачивать циклы, а затем реализовывать их в векторных инструкциях в глубоко ошибаетесь.
Задам вопрос иначе — а менее уродливого способа работы с матрицами разве нет? Разве красивое использование синтаксиса в дальнейшем (всего-то две операции будут выглядить красиво — сложение и умножение, для остальных придется лепить методы) стоит появления в исходнике таких кирпичей?
И в чем проблема молча все поправить, оттестить и отправить коммит на гитхабе?
Статья отличная!
Статья отличная!
Как говорил один сантехник, которого потом правда расстреляли, — «Тут систему менять надо» (в смысле — все переписывать).
Что касается контента статьи — ну учебник Немнюгина содержит все то же самое, правда издан 14 лет назад, в 2000 году.
Что касается избавления от ужаса, на данный момент я заметил следующее:
Авторский кирпич транслируется в 204 инструкции:
Банальное и очевидное решение в три цикла:
Транслируется в 104 инструкции:
Что я упускаю?
Что касается контента статьи — ну учебник Немнюгина содержит все то же самое, правда издан 14 лет назад, в 2000 году.
Что касается избавления от ужаса, на данный момент я заметил следующее:
Авторский кирпич транслируется в 204 инструкции:
Скрытый текст
.LFB3:
.cfi_startproc
vmovss 56(%rsi), %xmm7
movq %rdi, %rax
vmovss 60(%rsi), %xmm6
vmovss 60(%rdx), %xmm0
vmovss 8(%rdx), %xmm12
vmovss 24(%rdx), %xmm13
vmovss 48(%rsi), %xmm2
vmovss 12(%rdx), %xmm3
vmovss 52(%rsi), %xmm4
vmovss 28(%rdx), %xmm5
vmovss 44(%rdx), %xmm1
vmovss 40(%rdx), %xmm10
vmovss 56(%rdx), %xmm11
vmovss %xmm7, -8(%rsp)
vmovss %xmm6, -4(%rsp)
vmovss 4(%rdx), %xmm7
vmovss 20(%rdx), %xmm6
vmovss %xmm0, -28(%rsp)
vmovss %xmm12, -48(%rsp)
vmovss 36(%rdx), %xmm0
vmovss 52(%rdx), %xmm12
vmovss %xmm13, -44(%rsp)
vmovss 32(%rdx), %xmm13
vmovss %xmm2, -24(%rsp)
vmovss %xmm3, -20(%rsp)
vmovss %xmm4, -16(%rsp)
vmovss %xmm5, -12(%rsp)
vmovss %xmm1, -32(%rsp)
vmovss %xmm10, -72(%rsp)
vmovss (%rdx), %xmm1
vmovss %xmm11, -68(%rsp)
vmovss %xmm7, -64(%rsp)
vmovss %xmm6, -60(%rsp)
vmovss %xmm0, -56(%rsp)
vmovss %xmm12, -52(%rsp)
vmovss 16(%rdx), %xmm0
vmovss %xmm13, -40(%rsp)
vmovss (%rsi), %xmm13
vmovss 4(%rsi), %xmm12
vmulss %xmm13, %xmm1, %xmm15
vmovss 48(%rdx), %xmm14
vmovss %xmm14, -36(%rsp)
vmulss %xmm12, %xmm0, %xmm14
vmovss 8(%rsi), %xmm11
vmovss 12(%rsi), %xmm10
vmovss 16(%rsi), %xmm9
vaddss %xmm14, %xmm15, %xmm15
vmulss -40(%rsp), %xmm11, %xmm14
vmovss 20(%rsi), %xmm8
vmovss 24(%rsi), %xmm7
vmovss 28(%rsi), %xmm6
vmovss 32(%rsi), %xmm5
vmovss 36(%rsi), %xmm4
vaddss %xmm14, %xmm15, %xmm15
vmulss -36(%rsp), %xmm10, %xmm14
vmovss 40(%rsi), %xmm3
vmovss 44(%rsi), %xmm2
vaddss %xmm14, %xmm15, %xmm14
vmulss -64(%rsp), %xmm13, %xmm15
vmovss %xmm14, (%rdi)
vmulss -60(%rsp), %xmm12, %xmm14
vaddss %xmm14, %xmm15, %xmm15
vmulss -56(%rsp), %xmm11, %xmm14
vaddss %xmm14, %xmm15, %xmm15
vmulss -52(%rsp), %xmm10, %xmm14
vaddss %xmm14, %xmm15, %xmm14
vmulss -48(%rsp), %xmm13, %xmm15
vmovss %xmm14, 4(%rdi)
vmulss -44(%rsp), %xmm12, %xmm14
vaddss %xmm14, %xmm15, %xmm15
vmulss -72(%rsp), %xmm11, %xmm14
vmulss -32(%rsp), %xmm11, %xmm11
vaddss %xmm14, %xmm15, %xmm15
vmulss -68(%rsp), %xmm10, %xmm14
vmulss -28(%rsp), %xmm10, %xmm10
vaddss %xmm14, %xmm15, %xmm14
vmovss -20(%rsp), %xmm15
vmulss %xmm15, %xmm13, %xmm13
vmovss %xmm14, 8(%rdi)
vmovss -12(%rsp), %xmm14
vmulss %xmm14, %xmm12, %xmm12
vaddss %xmm12, %xmm13, %xmm13
vmovaps %xmm15, %xmm12
vaddss %xmm11, %xmm13, %xmm13
vmulss %xmm9, %xmm1, %xmm11
vaddss %xmm10, %xmm13, %xmm13
vmulss %xmm8, %xmm0, %xmm10
vaddss %xmm10, %xmm11, %xmm11
vmulss -40(%rsp), %xmm7, %xmm10
vmovss %xmm13, 12(%rdi)
vmovaps %xmm14, %xmm13
vaddss %xmm10, %xmm11, %xmm11
vmulss -36(%rsp), %xmm6, %xmm10
vaddss %xmm10, %xmm11, %xmm10
vmovss %xmm10, 16(%rdi)
vmulss -60(%rsp), %xmm8, %xmm10
vmulss -64(%rsp), %xmm9, %xmm11
vaddss %xmm10, %xmm11, %xmm11
vmulss -56(%rsp), %xmm7, %xmm10
vaddss %xmm10, %xmm11, %xmm11
vmulss -52(%rsp), %xmm6, %xmm10
vaddss %xmm10, %xmm11, %xmm10
vmulss -48(%rsp), %xmm9, %xmm11
vmulss %xmm15, %xmm9, %xmm9
vmovss -32(%rsp), %xmm15
vmovss %xmm10, 20(%rdi)
vmulss -44(%rsp), %xmm8, %xmm10
vmulss %xmm14, %xmm8, %xmm8
vmovss -48(%rsp), %xmm14
vaddss %xmm8, %xmm9, %xmm9
vmovss -40(%rsp), %xmm8
vaddss %xmm10, %xmm11, %xmm11
vmulss -72(%rsp), %xmm7, %xmm10
vmulss %xmm15, %xmm7, %xmm7
vaddss %xmm7, %xmm9, %xmm9
vmulss %xmm5, %xmm1, %xmm7
vaddss %xmm10, %xmm11, %xmm11
vmulss -68(%rsp), %xmm6, %xmm10
vaddss %xmm10, %xmm11, %xmm10
vmovss -28(%rsp), %xmm11
vmulss %xmm11, %xmm6, %xmm6
vmovss %xmm10, 24(%rdi)
vaddss %xmm6, %xmm9, %xmm9
vmulss %xmm4, %xmm0, %xmm6
vmovss -44(%rsp), %xmm10
vaddss %xmm6, %xmm7, %xmm7
vmulss %xmm8, %xmm3, %xmm6
vmovss %xmm9, 28(%rdi)
vmovss -36(%rsp), %xmm9
vaddss %xmm6, %xmm7, %xmm7
vmulss %xmm9, %xmm2, %xmm6
vaddss %xmm6, %xmm7, %xmm6
vmulss -64(%rsp), %xmm5, %xmm7
vmovss %xmm6, 32(%rdi)
vmulss -60(%rsp), %xmm4, %xmm6
vaddss %xmm6, %xmm7, %xmm7
vmulss -56(%rsp), %xmm3, %xmm6
vaddss %xmm6, %xmm7, %xmm7
vmulss -52(%rsp), %xmm2, %xmm6
vaddss %xmm6, %xmm7, %xmm6
vmulss %xmm14, %xmm5, %xmm7
vmulss %xmm12, %xmm5, %xmm5
vmovss %xmm6, 36(%rdi)
vmulss %xmm10, %xmm4, %xmm6
vmulss %xmm13, %xmm4, %xmm4
vaddss %xmm6, %xmm7, %xmm7
vmulss -72(%rsp), %xmm3, %xmm6
vaddss %xmm4, %xmm5, %xmm5
vmulss %xmm15, %xmm3, %xmm3
vmovss -16(%rsp), %xmm4
vmulss %xmm4, %xmm0, %xmm0
vaddss %xmm3, %xmm5, %xmm5
vmovss -64(%rsp), %xmm3
vaddss %xmm6, %xmm7, %xmm7
vmulss -68(%rsp), %xmm2, %xmm6
vmulss %xmm11, %xmm2, %xmm2
vaddss %xmm2, %xmm5, %xmm5
vmovss -24(%rsp), %xmm2
vmulss %xmm2, %xmm1, %xmm1
vaddss %xmm6, %xmm7, %xmm6
vmovaps %xmm12, %xmm7
vmovaps %xmm15, %xmm12
vmovaps %xmm8, %xmm15
vmovss -8(%rsp), %xmm8
vaddss %xmm0, %xmm1, %xmm1
vmulss %xmm8, %xmm15, %xmm0
vmovss %xmm5, 44(%rdi)
vmovaps %xmm9, %xmm15
vmovss %xmm6, 40(%rdi)
vmovss -4(%rsp), %xmm9
vmovss -60(%rsp), %xmm5
vmovaps %xmm13, %xmm6
vaddss %xmm0, %xmm1, %xmm1
vmulss %xmm9, %xmm15, %xmm0
vmovaps %xmm11, %xmm13
vaddss %xmm0, %xmm1, %xmm1
vmulss %xmm2, %xmm3, %xmm0
vmovss -56(%rsp), %xmm3
vmovss %xmm1, 48(%rdi)
vmulss %xmm4, %xmm5, %xmm1
vmovss -52(%rsp), %xmm5
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm8, %xmm3, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm9, %xmm5, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm2, %xmm14, %xmm1
vmovss %xmm0, 52(%rdi)
vmulss %xmm4, %xmm10, %xmm0
vmovss -72(%rsp), %xmm10
vmovss -68(%rsp), %xmm11
vaddss %xmm0, %xmm1, %xmm0
vmulss %xmm8, %xmm10, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm9, %xmm11, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm2, %xmm7, %xmm1
vmovss %xmm0, 56(%rdi)
vmulss %xmm4, %xmm6, %xmm0
vaddss %xmm0, %xmm1, %xmm0
vmulss %xmm8, %xmm12, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmulss %xmm9, %xmm13, %xmm1
vaddss %xmm1, %xmm0, %xmm0
vmovss %xmm0, 60(%rdi)
ret
.cfi_endproc
Банальное и очевидное решение в три цикла:
Скрытый текст
struct matint
{
float data[4][4];
};
class matrix4x4
{
public:
matrix4x4();
matrix4x4(const matint& data);
matrix4x4 operator*(matrix4x4 const& m) const;
void randInit();
void print() const;
private:
matint values;
};
matrix4x4 matrix4x4::operator*(matrix4x4 const &m) const
{
matint out;
for(size_t i=0;i<4;i++)
{
for(size_t j=0;j<4;j++)
{
out.data[j][i]=0.0;
for(size_t k=0;k<4;k++)
{
out.data[j][i]+=values.data[k][i]*m.values.data[j][k];
}
}
}
return{out};
}
Транслируется в 104 инструкции:
Скрытый текст
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rdi, %rax
movq %rsp, %rbp
.cfi_def_cfa_register 6
andq $-32, %rsp
addq $16, %rsp
vmovss 60(%rdx), %xmm4
vmovss 16(%rdx), %xmm12
vmovss 32(%rdx), %xmm8
vshufps $0, %xmm12, %xmm12, %xmm12
vmovss %xmm4, -112(%rsp)
vshufps $0, %xmm8, %xmm8, %xmm8
vmovups (%rsi), %xmm4
vbroadcastss (%rdx), %xmm14
vmulps %xmm12, %xmm4, %xmm12
vmovss 56(%rdx), %xmm3
vmulps %xmm14, %xmm4, %xmm14
vmovss %xmm3, -108(%rsp)
vmovups 16(%rsi), %xmm2
vxorps %xmm3, %xmm3, %xmm3
vmulps %xmm8, %xmm4, %xmm8
vmovss 20(%rdx), %xmm11
vmovss 36(%rdx), %xmm7
vbroadcastss 4(%rdx), %xmm15
vshufps $0, %xmm11, %xmm11, %xmm11
vshufps $0, %xmm7, %xmm7, %xmm7
vmulps %xmm15, %xmm2, %xmm15
vaddps %xmm3, %xmm14, %xmm14
vmovss 52(%rdx), %xmm1
vaddps %xmm3, %xmm12, %xmm12
vmulps %xmm2, %xmm11, %xmm11
vmovss 24(%rdx), %xmm10
vaddps %xmm3, %xmm8, %xmm8
vmulps %xmm2, %xmm7, %xmm7
vmovss 40(%rdx), %xmm6
vmovss %xmm1, -104(%rsp)
vshufps $0, %xmm10, %xmm10, %xmm10
vmovups 32(%rsi), %xmm1
vshufps $0, %xmm6, %xmm6, %xmm6
vaddps %xmm14, %xmm15, %xmm14
vbroadcastss 8(%rdx), %xmm13
vmulps %xmm10, %xmm1, %xmm10
vaddps %xmm7, %xmm8, %xmm7
vmovss 28(%rdx), %xmm9
vmulps %xmm13, %xmm1, %xmm13
vaddps %xmm11, %xmm12, %xmm11
vmovss 44(%rdx), %xmm5
vmulps %xmm6, %xmm1, %xmm6
vmovss 48(%rdx), %xmm0
vmovss %xmm0, -100(%rsp)
vshufps $0, %xmm9, %xmm9, %xmm9
vmovups 48(%rsi), %xmm0
vshufps $0, %xmm5, %xmm5, %xmm5
vaddps %xmm14, %xmm13, %xmm13
vbroadcastss 12(%rdx), %xmm14
vmulps %xmm9, %xmm0, %xmm9
vaddps %xmm6, %xmm7, %xmm6
vmulps %xmm0, %xmm14, %xmm14
vaddps %xmm10, %xmm11, %xmm10
vmulps %xmm5, %xmm0, %xmm5
vaddps %xmm14, %xmm13, %xmm15
vaddps %xmm5, %xmm6, %xmm5
vaddps %xmm9, %xmm10, %xmm9
vmovaps %xmm15, -96(%rsp)
movq -96(%rsp), %rdi
vmovaps %xmm15, -80(%rsp)
vmovaps %xmm9, -96(%rsp)
movq -96(%rsp), %rsi
vmovaps %xmm9, -64(%rsp)
movq %rdi, (%rax)
movq -72(%rsp), %rdi
vmovaps %xmm5, -96(%rsp)
vmovaps %xmm5, -48(%rsp)
vbroadcastss -100(%rsp), %xmm5
movq -96(%rsp), %rcx
movq %rsi, 16(%rax)
vmulps %xmm5, %xmm4, %xmm4
movq -56(%rsp), %rsi
movq %rdi, 8(%rax)
movq %rcx, 32(%rax)
movq -40(%rsp), %rcx
movq %rsi, 24(%rax)
vaddps %xmm3, %xmm4, %xmm3
movq %rcx, 40(%rax)
vbroadcastss -104(%rsp), %xmm4
vmulps %xmm4, %xmm2, %xmm2
vaddps %xmm2, %xmm3, %xmm2
vbroadcastss -108(%rsp), %xmm3
vmulps %xmm3, %xmm1, %xmm1
vaddps %xmm1, %xmm2, %xmm1
vbroadcastss -112(%rsp), %xmm2
vmulps %xmm2, %xmm0, %xmm0
vaddps %xmm0, %xmm1, %xmm7
vmovaps %xmm7, -96(%rsp)
movq -96(%rsp), %rdx
vmovaps %xmm7, -32(%rsp)
movq %rdx, 48(%rax)
movq -24(%rsp), %rdx
movq %rdx, 56(%rax)
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
Что я упускаю?
А что показывает секундомер?
Мы когда-то в школе ray tracing в Borland C 3.1 писали. Вектора и матрицы множили такими же конструкциями, но у нас значения лежали не в массиве, а в мемберах структуры вида m11, m12, m13… Но это было давно и неправда, и скорее всего тогда компилятор был сильно глупее.
Кстати, если заменить массив на именованные мемберы, сколько инструкций будет и что на секундомере?
Кстати, если заменить массив на именованные мемберы, сколько инструкций будет и что на секундомере?
Упускаете бессмысленность сравнения производительности двух вариантов по количеству инструкций, а не по таймингу.
Прогнал тесты. Итого:
Дает итог (значения меняются незначительно):
Разница почти в полтора-два раза в пользу циклов. Исходники на гитхабе.
ВАЖНО: При компиляции включить третий уровень оптимизации и AVX
Скрытый текст
#define itemCount (20000000)
void testMy()
{
//Сброс ГСЧ, чтобы числовые значения всегда повторялись
srand(0);
high_resolution_clock::time_point t1 = high_resolution_clock::now();
matrix4x4* out=new matrix4x4[itemCount];
matrix4x4 a;
out[0].randInit();
a.Init9999999();
for(size_t i=1;i<itemCount;i++)
{
out[i]=a*out[i-1];
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
std::cout<< duration <<std::endl;
out[itemCount-1].print();
delete[] out;
}
Дает итог (значения меняются незначительно):
698649 -- время работы кода с циклами в микросекундах
matrix(
[0.840188, 0.911647, 0.277775, 0.364784]
, [0.394383, 0.197551, 0.55397, 0.513401]
, [0.783099, 0.335223, 0.477397, 0.95223]
, [0.79844, 0.76823, 0.628871, 0.916195]
);
1091429 -- время работы расписанного кода без циклов в микросекундах
matrix(
[0.840188, 0.911647, 0.277775, 0.364784]
, [0.394383, 0.197551, 0.55397, 0.513401]
, [0.783099, 0.335223, 0.477397, 0.95223]
, [0.79844, 0.76823, 0.628871, 0.916195]
);
Разница почти в полтора-два раза в пользу циклов. Исходники на гитхабе.
ВАЖНО: При компиляции включить третий уровень оптимизации и AVX
Спасибо
А если без AVX? Просто мне тут для игры (Вангеры, там много софт рендринга) пришлось sse2 отключать так как были люди у которых его не было. :(
Так как после тех тестов я пару раз перезагрузил машину и обновил ядро, тест с AVX я переделал:
Значения в самой нижней строке — результат работы программы на настоящем Pentium III/1GHz. В этом тесте умножалось не двадцать миллионов матриц, а всего два миллиона, иначе бы старикану не хватило памяти.
Верхние три строки получены на машине с процессором:
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 58
model name : Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz
stepping : 9
microcode : 0x1b
cpu MHz : 849.468
cache size : 4096 KB
physical id : 0
siblings : 4
core id : 0
cpu cores : 2
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms
bogomips : 4788.82
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
Значения в самой нижней строке — результат работы программы на настоящем Pentium III/1GHz. В этом тесте умножалось не двадцать миллионов матриц, а всего два миллиона, иначе бы старикану не хватило памяти.
Увидеть характеристики процессора музейного экспоната:
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 8
model name : Pentium III (Coppermine)
stepping : 10
cpu MHz : 997.535
cache size : 256 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 sep mtrr pge mca cmov pse36 mmx fxsr sse up
bogomips : 1995.07
clflush size : 32
cache_alignment : 32
address sizes : 36 bits physical, 32 bits virtual
Хоть я и согласен с вами, но я думаю что вопросы импрувмента кода лучше обсуждать лично, если конечно статья не о написании идеального кода :)
PS. Ну, в ручном режиме можно и до 68-ми инструкций дожать ;) (а если еще и память выровнять, да movaps везде....)
PS. Ну, в ручном режиме можно и до 68-ми инструкций дожать ;) (а если еще и память выровнять, да movaps везде....)
См. под кат
movups xmm0, [eax]
movups xmm1, [eax + 16]
movups xmm2, [eax + 32]
movups xmm3, [eax + 48]
movups xmm4, [ebx]
movaps xmm5, xmm4
movaps xmm6, xmm4
movaps xmm7, xmm4
shufps xmm4, xmm4, 00000000b
shufps xmm5, xmm5, 01010101b
shufps xmm6, xmm6, 10101010b
shufps xmm7, xmm7, 11111111b
mulps xmm4, xmm0
mulps xmm5, xmm1
mulps xmm6, xmm2
mulps xmm7, xmm3
addps xmm6, xmm7
addps xmm4, xmm5
addps xmm4, xmm6
movups [ecx], xmm4
movups xmm4, [ebx + 16]
movaps xmm5, xmm4
movaps xmm6, xmm4
movaps xmm7, xmm4
shufps xmm4, xmm4, 00000000b
shufps xmm5, xmm5, 01010101b
shufps xmm6, xmm6, 10101010b
shufps xmm7, xmm7, 11111111b
mulps xmm4, xmm0
mulps xmm5, xmm1
mulps xmm6, xmm2
mulps xmm7, xmm3
addps xmm6, xmm7
addps xmm4, xmm5
addps xmm4, xmm6
movups [ecx + 16], xmm4
movups xmm4, [ebx + 32]
movaps xmm5, xmm4
movaps xmm6, xmm4
movaps xmm7, xmm4
shufps xmm4, xmm4, 00000000b
shufps xmm5, xmm5, 01010101b
shufps xmm6, xmm6, 10101010b
shufps xmm7, xmm7, 11111111b
mulps xmm4, xmm0
mulps xmm5, xmm1
mulps xmm6, xmm2
mulps xmm7, xmm3
addps xmm6, xmm7
addps xmm4, xmm5
addps xmm4, xmm6
movups [ecx + 32], xmm4
movups xmm4, [ebx + 48]
movaps xmm5, xmm4
movaps xmm6, xmm4
movaps xmm7, xmm4
shufps xmm4, xmm4, 00000000b
shufps xmm5, xmm5, 01010101b
shufps xmm6, xmm6, 10101010b
shufps xmm7, xmm7, 11111111b
mulps xmm4, xmm0
mulps xmm5, xmm1
mulps xmm6, xmm2
mulps xmm7, xmm3
addps xmm6, xmm7
addps xmm4, xmm5
addps xmm4, xmm6
movups [ecx + 48], xmm4
Сразу видно код «ручной работы», все так ровненько. Тут надо посмотреть, как он на кэш и конвейер ляжет.
Конкатенация матриц не сильно частая операция, в софтвер-рендере лучше оптимизировать другие части — например трансформацию вершин. Тут руками можно чудес наделать, несколько вершин (4?) за итерацию преобразовывать, да еще и в кеш префетчить на упреждение…
Умножение пары матриц 4х4 — это полная трансформация вершин четырехугольника.
А за что минус?
Я согласен с вами, однако по сравнению другими операциями — не такая уж и частая, согласитесь.
Я написал из своего опыта — что оптимизация конвеерных функций (тот же TnL), которые выполняют одни и теже операции, и часть данных константна — там можно получить сещественный прирост при правильной ручной оптимизации.
Я согласен с вами, однако по сравнению другими операциями — не такая уж и частая, согласитесь.
Я написал из своего опыта — что оптимизация конвеерных функций (тот же TnL), которые выполняют одни и теже операции, и часть данных константна — там можно получить сещественный прирост при правильной ручной оптимизации.
Что касается вопросов, связанных с обсуждением кодов — на мой взгляд, на профильном техническом ресурсе обнаружение и обсуждение скользких мест, недостатков, плохих практик и так далее должно вестись наиболее широко и гласно — это говорит о том, что аудитория ресурса откровенный ужас мимо себя просто так не пропустит.
Не стоит, согласен. Нужно поправить. Но этот пост не из хаба «идеальный код», и на него не претендует, поэтому можете смело сделать фикс и отправить пулл реквест, если хотите :)
В комментарии выше я предлагаю вариант исправления кошмарика.
Прошу простить мой резкий тон, был шокирован.
Прошу простить мой резкий тон, был шокирован.
Для примеров лучше использовать код попроще и попонятнее — в три цикла.
Для реальности — нужно мерить, сравнивать и оптимизировать обоснованно.
И уж никак не предлагать такой код в качестве учебных примеров.
P.S. Это все IMHO, забейте — статья отличная! :-)
Для реальности — нужно мерить, сравнивать и оптимизировать обоснованно.
И уж никак не предлагать такой код в качестве учебных примеров.
P.S. Это все IMHO, забейте — статья отличная! :-)
Можно использовать и готовую библиотеку матриц, например Eigen (там и SIMD встроено).
Немного не в тему, но хочу поделиться ссылкой на интересную реализацию программного рендера: www.ixbt.com/video/hier-tiling.html
Кстати программный рендер используется не только в академических целях. В ZBrush, например, используется полностью программный рендер, при этом количество полигонов при отрисовке может достигать нескольких десятков миллионов.
Кстати программный рендер используется не только в академических целях. В ZBrush, например, используется полностью программный рендер, при этом количество полигонов при отрисовке может достигать нескольких десятков миллионов.
Простите пожалуйста, не могли бы поделиться источником инфы про софтверный рендер в ZBrush? Дико интересно.
Да собственно официальный сайт. Алгоритм по понятным причинам не доступен, но сам факт того, что там используется программный рендер написано на сайте:
pixologic.com/zbrush/system/
ZBrush is software rendered, meaning that ZBrush itself is doing the rendering rather than the GPU. Your choice of GPU will not matter so long as it supports the recommended monitor resolution.
CPU: P4 or AMD Opteron or Athlon64 Processor (Must have SSE2: Streaming SIMD Extensions 2)
pixologic.com/zbrush/system/
<ИМХО> Software Render уже разобран и в статьях и в книгах.
Прошло даже несколько конкурсов на эту тему, например:
www.gamedev.ru/code/forum/?id=126749
Статья ничего нового не дает. </ИМХО>
Прошло даже несколько конкурсов на эту тему, например:
www.gamedev.ru/code/forum/?id=126749
Статья ничего нового не дает. </ИМХО>
Хороший учебник с примерами.
Всем начинающим пионерам рекомендую отличную книгу Роджерса, прочитав которую, вы станете великим в деле компьютерной графики до конца дней своих. Единственное, что в ней не хватает — это bumpmapping-а всякого и работы с текстурами.
Всем начинающим пионерам рекомендую отличную книгу Роджерса, прочитав которую, вы станете великим в деле компьютерной графики до конца дней своих. Единственное, что в ней не хватает — это bumpmapping-а всякого и работы с текстурами.
Большое спасибо за статью! Я как раз уже давно хотел написать софтрендерер, но с матчастью были проблемы (осилил пока только простой Wireframe-рендеринг без корректного отсечения), жду следующих частей статьи.
А потом переписать всё под OpenCL!
Довольно избитая тема. Любопытно будет посмотреть на реализацию растеризации, есть там подводные камни.
Подача материала в разных статьях и соответствующей литературе существенно различается. Всегда хорошо иметь выбор, что почитать на эту тему. Кому-то, возможно, приглянется мой вариант
Хорошо бы, если бы вы писали, какой у Вас FPS получается на каждом этапе, для сравнения с другими подобными реализациями на других языках и оценки оверхеда от каждого этапа конвеера (с указанием процессора, количества вершин в модели и размеров вьюпорта). К примеру, в wireframe какой получился?
Алгебраично!
У меня есть такой глупый-глупый вопрос: а почему в 3д всегда используют трехточечную проекцию?
Или, иными словами, почему при малом угле наклона вертикальные линии не вертикальны? Я понимаю, что математика, но почему именно такая? Завалы вертикалей — это же плохо, не?
Или, иными словами, почему при малом угле наклона вертикальные линии не вертикальны? Я понимаю, что математика, но почему именно такая? Завалы вертикалей — это же плохо, не?
Может, я неверно понял вопрос, но разве кол-во точек схода в итоговом изображении не зависит от ориентации камеры? Если плоскость проекции пересекает все три оси, то мы получим трехточечную проекцию, но мы так же можем получить двух или одно-точечную, в зависимости от угла просмотра
Я к тому, что в рисовании принято избегать трехточечной проекции без серьёзных оснований. Нет ли таких систем рендеринга, которые бы выдерживали это правило? И более того, использовали бы более одного горизонта для помещений, и другие трюки из ассортимента художников. Вон, bloom уже используют…
Проблема изложена например в сто первом параграфе Ководства А. Лебедева.
Это, может быть, и плохо с «эстетической» точки зрения, зато абсолютно реалистично и соответствует действительно видимой картинке. К примеру, можете сфотографировать высокое здание, стоя не очень далеко от него — если вы поднимете камеру чуть-чуть вверх от горизонта при этом, увидите, что вертикальные стены здания сходятся. Это нормально.
Слегка неформальное объяснение: подъем камеры вверх — то же самое, что опускание всей сцены вниз, и вертикальные линии становятся, например, почти горизонтальными или даже строго горизонтальными (если угол равен 90 градусам). А параллельные горизонтальные (да и вообще какие угодно) линии становятся все ближе и ближе при приближении к горизонту из-за того, что расстояние между ними хоть и неизменно, но становится меньше в пересчете на угловой размер при удалении от наблюдателя.
Слегка неформальное объяснение: подъем камеры вверх — то же самое, что опускание всей сцены вниз, и вертикальные линии становятся, например, почти горизонтальными или даже строго горизонтальными (если угол равен 90 градусам). А параллельные горизонтальные (да и вообще какие угодно) линии становятся все ближе и ближе при приближении к горизонту из-за того, что расстояние между ними хоть и неизменно, но становится меньше в пересчете на угловой размер при удалении от наблюдателя.
Не соответствует оно никакой картинке. Проекция изображения в глазу идёт не на плоскость, а на вогнутую полусферу (неровной формы). Дальше на это накладываются алгоритмы компрессии, плюс алгоритм восстановления изображения, включающий в себя восстановление прямых.
Вот, простое вам упражнение: подойдите к столбу, и внимательно на него посмотрите, держа голову (глаза) прямо. Лучше с чем-то типа зубочистки для оценки относительных размеров. Столб будет толще в середине и тоньше к верху/низу (понятно почему — расстояние до центра столба меньше, чем до верха/низа). Таким образом вместо прямой линии будет кривая. И эти кривые — вокруг все. Но мозги их исправляют — и, заодно, исправляют мелкий угол от проекции, представляя прямоугольные объекты прямоугольными.
Ещё более важная вещь в рисовании: использование нескольких горизонтов. Это когда пол сходится к одному, а потолок к другому. Для человеческого глаза этот формат рисунка правильнее, чем формальное построение.
Вот, простое вам упражнение: подойдите к столбу, и внимательно на него посмотрите, держа голову (глаза) прямо. Лучше с чем-то типа зубочистки для оценки относительных размеров. Столб будет толще в середине и тоньше к верху/низу (понятно почему — расстояние до центра столба меньше, чем до верха/низа). Таким образом вместо прямой линии будет кривая. И эти кривые — вокруг все. Но мозги их исправляют — и, заодно, исправляют мелкий угол от проекции, представляя прямоугольные объекты прямоугольными.
Ещё более важная вещь в рисовании: использование нескольких горизонтов. Это когда пол сходится к одному, а потолок к другому. Для человеческого глаза этот формат рисунка правильнее, чем формальное построение.
Не соглашусь. Неуместно сравнивать получаемую картинку с проекцией на глаз человека, нужно сравнивать с проекцией на экран, на который мы смотрим. Представьте, что вы смотрите на какую-то трехмерную сцену через «окно». Точки сцены проецируются на плоскость этого окна (= экрана). А уж и то, что получается на настоящем окне, и то, что получается на экране монитора, глаз человека спроецирует одинаково (то есть применительно к данной ситуации — вообще неважно, как именно, хоть вверх ногами в мозг).
Причина того, что отрендеренные картинки (и фотографии!) содержат то, что многие называют перспективными «искажениями» — несоответствие углу обзора. У современных камер горизонтальный угол обзора — что-то в районе 90 градусов, в играх тоже используются похожие значения. А смотрим мы на эту картинку на мониторе потом с такого расстояния, что ее угловой размер — градусов 40. Конечно, перспектива «плывет». В свою очередь, предложу вам свой эксперимент в ответ: возьмите картинку, FOV которой вы точно знаете, и расположитесь относительно нее так, чтобы FOV совпадал. В этом случае, говоря, опять же, «простым» языком, на все те вытянутые и «искаженные» детали ближе к краям картинки вы будете смотреть под таким (правильным) углом, что они сожмутся обратно в естественную с точки зрения вашего взгляда форму.
На ваш пример со столбом могу сказать, что уменьшение деталей с удалением от точки обзора (верх/низ столба) как раз и компенсируется тем, что на те «участки картинки», которые содержат эти точки столба, мы смотрим под таким углом, что, если отойти подальше и уменьшить свой угол обзора, они будут казаться растянутыми (см. предыдущий параграф).
Разумеется, это всё неприменимо к изогнутым экранам. Именно поэтому, в частности, в Oculus Rift картинка такая «выпученная», если ее перенести с рифтовского изогнутого экрана на плоский экран монитора.
Причина того, что отрендеренные картинки (и фотографии!) содержат то, что многие называют перспективными «искажениями» — несоответствие углу обзора. У современных камер горизонтальный угол обзора — что-то в районе 90 градусов, в играх тоже используются похожие значения. А смотрим мы на эту картинку на мониторе потом с такого расстояния, что ее угловой размер — градусов 40. Конечно, перспектива «плывет». В свою очередь, предложу вам свой эксперимент в ответ: возьмите картинку, FOV которой вы точно знаете, и расположитесь относительно нее так, чтобы FOV совпадал. В этом случае, говоря, опять же, «простым» языком, на все те вытянутые и «искаженные» детали ближе к краям картинки вы будете смотреть под таким (правильным) углом, что они сожмутся обратно в естественную с точки зрения вашего взгляда форму.
На ваш пример со столбом могу сказать, что уменьшение деталей с удалением от точки обзора (верх/низ столба) как раз и компенсируется тем, что на те «участки картинки», которые содержат эти точки столба, мы смотрим под таким углом, что, если отойти подальше и уменьшить свой угол обзора, они будут казаться растянутыми (см. предыдущий параграф).
Разумеется, это всё неприменимо к изогнутым экранам. Именно поэтому, в частности, в Oculus Rift картинка такая «выпученная», если ее перенести с рифтовского изогнутого экрана на плоский экран монитора.
В статье слишком много матана и по сути нет рендера.
Не так давно тоже баловался mentalx.org/tmp/metla_2014_09_22.7z 31к полигонов.
Рендер треугольника с освещением в SSE переписал, как оказалось, не зря, т.к. получил свои 10% к FPS по сравнению с SSE оптимизацией компилятора 8)
Не так давно тоже баловался mentalx.org/tmp/metla_2014_09_22.7z 31к полигонов.
Рендер треугольника с освещением в SSE переписал, как оказалось, не зря, т.к. получил свои 10% к FPS по сравнению с SSE оптимизацией компилятора 8)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Software renderer — 1: матчасть