Pull to refresh

Comments 24

result = (keys[i] == target) ? values[i] : result;

Теперь values[i] читается на каждой итерации безусловно

С чего бы это? Как по вашему, упадет ли такой код: int c = b != 0 ? a/b : 0;

Неплохо бы ассемблерный код в примерах приводить - процессор всё таки не C исполняет ) У меня gcc -O3 на

result = (keys[i] == target) ? values[i] : result;

генерирует обычное условие (подозреваю, что в других примерах с тернарным оператором будет так же). И если таки сгенерировать branchless код (используя CMOV), values будет читаться только когда надо (скорее наоборот, исчезнут лишние спекулятивные чтения).

Компилятор может превратить это в CMOV

cmp keys[i], target
cmov result, values[i]

Но это не гарантия и может сделать так

cmp keys[i], target
jne skip
mov result, values[i]

сделав обычное ветвление, это тоже зависит от включенных оптимизаций, целевой архитектуры, вероятности ветки по собранной эвристике и стоимости операций в этом месте. А еще это зависит от данных, и если VALUES[i] требует загрузки из памяти, то предпочитаетс JNE именно потому что CMOV в этом случае всё равно должен загрузить значение до выполнения, и латенси памяти попадает в хотпас независимо от условия. То есть CMOV с load-зависимостью иногда будет хуже, чем ветвление и компилятор это знает.
CMOV условно перемещает значение из источника в регистр, но источник должен быть УЖЕ готов до выполнения, сам CMOV не может условно отменить уже начатую загрузку из памяти, а только условно записывает результат.

CMOV в этом случае всё равно должен загрузить значение до выполнения

Не должен и не имеет права - потенциально загрузка может сгенерировать исключение (если адрес невалидный), при ложном условии этого быть не должно. CMOV сам инициирует загрузку из памяти после проверки условия.

Если явно указать компилятору более-менее современную архитектуру, генерируется векторный вариант с vpcmp/vpmaskmov - без бранчей, но опять же грузятся только элементы вектора, для которых условие сработало.

PS Таки прочитал документацию - вы правы, "CMOVcc loads data from its source operand into a temporary register unconditionally (regardless of the condition code and the status flags in the EFLAGS register)". И по тексту дальше генерация исключения от условия не зависит. Но тогда получается, что компилировать тернарный оператор в CMOV с аргументом в памяти нельзя - по семантике C/C++ выражение, соотвествующее невыполненному условию, не вычисляется, и никаких сторонних эффектов типа падения при разыменовении некорректного указателя быть не должно.

Процессор при branch prediction спекулятивно читает данные ещё до разрешения условия, но результат спекулятивного load при page fault не коммитится в retired состояние и исключение откладывается до момента, когда ветка "станет настоящей", что не противоречит обычному исполнению, неважно прочитали вы нулл из обычной ветки или спекулятивной.
Но есть другой нюанс, что компилятор может использовать CMOVcc только если "доказал" что оба оберанда валидны

Да, при спекулятивном выполнении проблем нет. Но для CMOV явно сказано, что исключение генерируется при невалидном адресе независимо от условия.

Для VPMASKMOV чётко сказано "Faults occur only due to mask-bit required memory accesses that caused the faults" - то есть если VPCMP не выставило бит, исключения точно не будет, так что компилятор спокойно может такое использовать. В принципе процессор может пытаться зачитать данные в любом случае, откладывая исключение как при спекулятивном выполнении - делает он так или сначала проверяет условие можно проверить микробенчмарками.

никаких сторонних эффектов типа падения при разыменовении некорректного указателя быть не должно.

Так в С++ разыменовывания некорректного указателя никогда не бывает (а там, где бывает – это как бы и не C++), поэтому компилятор спокойно тут пользуется CMOV-ом и вы недоуменно ловите исключение.

В случае, если это разыменование идёт в неисполняемой ветке тернарного оператора - behavior вполне себе defined и никакого исключения быть не должно. Поэтому компилятор не использует CMOV.

С производительностью примерно понятно, а что обычно выгоднее с точки зрения энергоэффективности? Медленное выполнение программы без предсказаний, или быстрое выполнение с некоторым количеством бесполезных операций?

Ну обычно мы тут в игрушки играем :) И новомодные E-коры ведут себя крайне коварно начиная тротлить в самый неподходящий момент, если вы про это. Поэтому основные потоки все забинжены на P-ядра, а на E-отдается всякая мелочь, вроде подгрузки текстур, распаковки банков и т.д. На консолях можно выставлять желаемую частоту ядра, если очень хочется, но я таких изысков пока не встречал. Задается максимум для двух первых ядер, а остальные под ОС живут.

Современные игровые движки действительно явно выставляют affinity для разных типов потоков с учётом гетерогенности? Интересно - согласен, что для стабильной работы это необходимо, но надо же спциально заморочиться...

Когда у вас на E-core частота скачет от 100Мгц до 2Ггц вас мало что спасет, поэтому туда только таски, которым пофиг на задержки

Не, я чуть про другое. Вот нужно написать код, которые выполняет определенную задачу, израсходовав минимальное количество энергии из аккумулятора. Как мы будем его писать - с ветвлениями и предсказаниями, потратив лишнюю энергию на оказавшиеся ненужными инструкции и обращения к памяти? Или линейно, потратив лишнее время на ожидание данных из памяти?

По опыту на батарейку смотрят в самую последнюю очередь (я про игры), если вашей батарейки хватает на 15 минут игры (примерно столько сейчас длится одна сессия), то никто даже не будет про это думать.

В ИСП РАН раньше занимались такими вопросами по заказу от одного крупного производителя смартфонов на Android: в итоге там пришли к выводу, что единственный реально эффективный способ достичь экономии при потреблении энергии на мобильном устройстве на 10–25%,‑ это не модифицировать код на C/C++, а модифицировать gcc, так чтобы он генерировал энергоэффективный код. Стоит поискать не выкладывали ли они в публичный доступ свои наработки, но они точно использовались и статей они публиковали по теме очень много.

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

Неплохо бы конечно такое заапстримить в основную ветку (и ввести отдельную подопцию -O для энергоэффективности)...

пойду думать над покупкой книги или курса, раз скидку дают

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

это просто всё обесценило, что было написано

мы смотрим предсказание/не предсказание в отрыве от реальности, и название статьи называется - Лишние вычисления, но как только мы ступаем на путь написания движка, мы вынуждены делать эти вычисления, чтобы были определенные состаяния, опять нету конкретики, нету рендер док, какой-то конкретики, полного состояния демки так сказать - всё оторвано, там на самом деле нужны все эти вычисления, и вы можете выбирать где будет кватернион, а где угол yaw-pitch, и это будет так углубляясь в тематику движков, и от этой точки захватывая все ускоряющие структуры(а их придётся захватывать потомучто это корень конструкции, даже грид в майнкрафте ускоряющий) становится понятно, что показывает NVIDIA на конференциях или крутые студии на тех же конференциях, а тут одна теория, подводя итог - важно всё со всем а не только пул или алокатор, важны состояния, да без выделений памяти ничего не будет работать это звучит как машине не нужен бензин

--

даже если подключаться к облаку вы всё равно сделаете аллокацию, тоесть аллокация будет в любом случае, наверно подразумевается что-то другое, но вобщем, состояние мира в любом случае будет подгружаться и в любом случае в память, и пока круче толстого клиента еще ничего нету

--

4гигабайта это было еще 2016 в одной известной игре, наверно только Quake, Arena или Dagerfall полетят менее чем на 4 гигабайтах

попробуйте Раст, С++ даже с модулями, это действительно уже догоняющий момент, Раст щас рвёт топ, там бойлерплейта поменьше

модули удобные, интерфейсы удобные, портянка на С++ даже на модулях близка к нечитаемости

я просто считаю, когда рассказывают о таких конструкциях в геймдеве контекстные!, которые затрагивают что-то более менее конкретное, то чтобы было понятно о чем речь читателям надо иногда подтверждать контекст визуально

например полет частичек снизу показан

Скрытый текст
.section .text.math3d::geometry::particle::ParticleSystem::spawn_spark_burst,"ax",@progbits
	.p2align	4
.type	math3d::geometry::particle::ParticleSystem::spawn_spark_burst,@function
math3d::geometry::particle::ParticleSystem::spawn_spark_burst:
	.cfi_startproc
	.cfi_personality 155, DW.ref.rust_eh_personality
	.cfi_lsda 27, .Lexception20
	push rbp
	.cfi_def_cfa_offset 16
	push r15
	.cfi_def_cfa_offset 24
	push r14
	.cfi_def_cfa_offset 32
	push r13
	.cfi_def_cfa_offset 40
	push r12
	.cfi_def_cfa_offset 48
	push rbx
	.cfi_def_cfa_offset 56
	sub rsp, 72
	.cfi_def_cfa_offset 128
	.cfi_offset rbx, -56
	.cfi_offset r12, -48
	.cfi_offset r13, -40
	.cfi_offset r14, -32
	.cfi_offset r15, -24
	.cfi_offset rbp, -16
	mov qword ptr [rsp + 56], rsi
	mov r14, qword ptr [rdi + 16]
	mov qword ptr [rsp + 64], rdx
	lea rax, [r14 + rdx]
	cmp rax, qword ptr [rdi + 24]
	ja .LBB59_22
	mov r12, rdi
	cmp byte ptr fs:[rand::rngs::thread::THREAD_RNG_KEY::{{constant}}::{{closure}}::__RUST_STD_INTERNAL_VAL@TPOFF+8], 1
	jne .LBB59_3
	mov rax, qword ptr fs:[0]
	lea rax, [rax + rand::rngs::thread::THREAD_RNG_KEY::{{constant}}::{{closure}}::__RUST_STD_INTERNAL_VAL@TPOFF]
.LBB59_4:
	mov rbx, qword ptr [rax]
	inc qword ptr [rbx]
	je .LBB59_5
	lea r13, [rbx + 16]
	lea rbp, [rbx + 288]
	xor r15d, r15d
	jmp .LBB59_10
	.p2align	4
.LBB59_70:
	mov rax, qword ptr [r12 + 8]
	lea rcx, [r14 + 2*r14]
	shl rcx, 4
	movaps xmm0, xmmword ptr [rip + .LCPI59_10]
	movups xmmword ptr [rax + rcx], xmm0
	mov rsi, qword ptr [rsp + 56]
	mov rdx, qword ptr [rsi]
	mov qword ptr [rax + rcx + 16], rdx
	mov edx, dword ptr [rsi + 8]
	mov dword ptr [rax + rcx + 24], edx
	movaps xmm0, xmmword ptr [rsp + 32]
	movlps qword ptr [rax + rcx + 28], xmm0
	movss xmm0, dword ptr [rsp + 8]
	movss dword ptr [rax + rcx + 36], xmm0
	movss xmm0, dword ptr [rsp + 16]
	movss dword ptr [rax + rcx + 40], xmm0
	movss xmm0, dword ptr [rsp + 12]
	movss dword ptr [rax + rcx + 44], xmm0
	inc r14
	mov qword ptr [r12 + 16], r14
	cmp r15, qword ptr [rsp + 64]
	je .LBB59_7
.LBB59_10:
	inc r15
	mov rax, qword ptr [rbx + 272]
	jmp .LBB59_11
	.p2align	4
.LBB59_15:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_16:
	xor eax, eax
.LBB59_17:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm1, ecx
	movss xmm0, dword ptr [rip + .LCPI59_0]
	addss xmm1, xmm0
	addss xmm1, xmm1
	addss xmm1, xmm0
	movss xmm0, dword ptr [rip + .LCPI59_1]
	movaps xmmword ptr [rsp + 32], xmm1
	ucomiss xmm0, xmm1
	ja .LBB59_18
.LBB59_11:
	cmp rax, 64
	jb .LBB59_17
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_15
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_15
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_16
	.p2align	4
.LBB59_33:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_34:
	xor eax, eax
.LBB59_35:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm1, ecx
	movss xmm0, dword ptr [rip + .LCPI59_0]
	addss xmm1, xmm0
	addss xmm1, xmm1
	addss xmm1, xmm0
	movss xmm0, dword ptr [rip + .LCPI59_1]
	ucomiss xmm0, xmm1
	ja .LBB59_36
.LBB59_18:
	cmp rax, 64
	jb .LBB59_35
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_33
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_33
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_34
	.p2align	4
.LBB59_36:
	movaps xmmword ptr [rsp + 16], xmm1
	jmp .LBB59_37
	.p2align	4
.LBB59_41:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_42:
	xor eax, eax
.LBB59_43:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm1, ecx
	movss xmm0, dword ptr [rip + .LCPI59_0]
	addss xmm1, xmm0
	addss xmm1, xmm1
	addss xmm1, xmm0
	movss xmm0, dword ptr [rip + .LCPI59_1]
	ucomiss xmm0, xmm1
	ja .LBB59_44
.LBB59_37:
	cmp rax, 64
	jb .LBB59_43
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_41
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_41
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_42
	.p2align	4
.LBB59_44:
	movss dword ptr [rsp + 8], xmm1
	jmp .LBB59_45
	.p2align	4
.LBB59_49:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_50:
	xor eax, eax
.LBB59_51:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm2, ecx
	addss xmm2, dword ptr [rip + .LCPI59_0]
	mulss xmm2, dword ptr [rip + .LCPI59_2]
	addss xmm2, dword ptr [rip + .LCPI59_3]
	movss xmm0, dword ptr [rip + .LCPI59_4]
	ucomiss xmm0, xmm2
	ja .LBB59_52
.LBB59_45:
	cmp rax, 64
	jb .LBB59_51
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_49
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_49
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_50
	.p2align	4
.LBB59_52:
	movaps xmm1, xmmword ptr [rsp + 32]
	unpcklps xmm1, xmmword ptr [rsp + 16]
	movss xmm0, dword ptr [rsp + 8]
	mulss xmm0, xmm2
	movss dword ptr [rsp + 8], xmm0
	shufps xmm2, xmm2, 0
	mulps xmm2, xmm1
	movaps xmmword ptr [rsp + 32], xmm2
	jmp .LBB59_53
	.p2align	4
.LBB59_57:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_58:
	xor eax, eax
.LBB59_59:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm1, ecx
	addss xmm1, dword ptr [rip + .LCPI59_0]
	addss xmm1, dword ptr [rip + .LCPI59_5]
	movss xmm0, dword ptr [rip + .LCPI59_6]
	ucomiss xmm0, xmm1
	ja .LBB59_60
.LBB59_53:
	cmp rax, 64
	jb .LBB59_59
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_57
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_57
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_58
	.p2align	4
.LBB59_60:
	movss dword ptr [rsp + 16], xmm1
	jmp .LBB59_61
	.p2align	4
.LBB59_65:
	mov rdi, rbp
	mov rsi, r13
	call rand::rngs::adapter::reseeding::ReseedingCore<R,Rsdr>::reseed_and_generate
.LBB59_66:
	xor eax, eax
.LBB59_67:
	mov ecx, dword ptr [rbx + 4*rax + 16]
	inc rax
	mov qword ptr [rbx + 272], rax
	shr ecx, 9
	or ecx, 1065353216
	movd xmm1, ecx
	addss xmm1, dword ptr [rip + .LCPI59_0]
	mulss xmm1, dword ptr [rip + .LCPI59_7]
	addss xmm1, dword ptr [rip + .LCPI59_8]
	movss xmm0, dword ptr [rip + .LCPI59_9]
	ucomiss xmm0, xmm1
	ja .LBB59_68
.LBB59_61:
	cmp rax, 64
	jb .LBB59_67
	mov rdx, qword ptr [rip + rand::rngs::adapter::reseeding::fork::RESEEDING_RNG_FORK_COUNTER]
	mov rax, qword ptr [rbx + 344]
	test rax, rax
	jle .LBB59_65
	cmp qword ptr [rbx + 352], rdx
	js .LBB59_65
	add rax, -256
	mov qword ptr [rbx + 344], rax
	mov rdi, rbp
	mov rsi, r13
	call rand_chacha::guts::refill_wide
	jmp .LBB59_66
	.p2align	4
.LBB59_68:
	movss dword ptr [rsp + 12], xmm1
	cmp r14, qword ptr [r12]
	jne .LBB59_70
	mov rdi, r12
	call alloc::raw_vec::RawVec<T,A>::grow_one
	jmp .LBB59_70
.LBB59_7:
	dec qword ptr [rbx]
	je .LBB59_8
.LBB59_22:
	add rsp, 72
	.cfi_def_cfa_offset 56
	pop rbx
	.cfi_def_cfa_offset 48
	pop r12
	.cfi_def_cfa_offset 40
	pop r13
	.cfi_def_cfa_offset 32
	pop r14
	.cfi_def_cfa_offset 24
	pop r15
	.cfi_def_cfa_offset 16
	pop rbp
	.cfi_def_cfa_offset 8
	ret
.LBB59_8:
	.cfi_def_cfa_offset 128
	mov rdi, rbx
	add rsp, 72
	.cfi_def_cfa_offset 56
	pop rbx
	.cfi_def_cfa_offset 48
	pop r12
	.cfi_def_cfa_offset 40
	pop r13
	.cfi_def_cfa_offset 32
	pop r14
	.cfi_def_cfa_offset 24
	pop r15
	.cfi_def_cfa_offset 16
	pop rbp
	.cfi_def_cfa_offset 8
	jmp alloc::rc::Rc<T,A>::drop_slow
.LBB59_3:
	.cfi_def_cfa_offset 128
	mov rax, qword ptr fs:[0]
	lea rdi, [rax + rand::rngs::thread::THREAD_RNG_KEY::{{constant}}::{{closure}}::__RUST_STD_INTERNAL_VAL@TPOFF]
	call std::sys::thread_local::native::lazy::Storage<T,D>::get_or_init_slow
	test rax, rax
	jne .LBB59_4
	call std::thread::local::panic_access_error
.LBB59_5:
	ud2
	jmp .LBB59_30
	jmp .LBB59_30
	jmp .LBB59_30
	jmp .LBB59_30
	jmp .LBB59_30
	jmp .LBB59_30
.LBB59_30:
	mov r14, rax
	dec qword ptr [rbx]
	jne .LBB59_32
	mov rdi, rbx
	call alloc::rc::Rc<T,A>::drop_slow
.LBB59_32:
	mov rdi, r14
	call _Unwind_Resume@PLT

понимаете? где тут нда в контексте бсп и бвх и полета частичек с физикой?

вы показали время, но хорошо бы показать плохой эффект визуально и хороший эффект визуально

вы показывали аллокаторы хорошо бы показать визульано как это отражается, вы показывали стрелу хорошо бы и её показать, иначе связь с геймдевом теряется и вы просто обсуждаете как кодить? тоесть не показываете то о чем говорите, тоесть нету подтверждения ваших взглядов в статьях вами самими, от этого не понятно как классифицировать что обсуждаем, почему это важно? потомучто С/С++ такой - любит конкретику, иначе это просто бесконечные формулировки кодьте так и всё

--

получается как раз в программировании и надо стараться четче показать пример, и четче сформулировать, о чем говорим, а просто время, которое все и так знают это не о чем просто

--

потом не понятна тогда мотивация? зачем мы вечно обсуждаем пулы - аллокации - время - для чего? вот когда вы обозревали движки вы показали только архитектуру, но в контексте сделать нету ничего, ни одного примера, получается читатель просто загуглит то что его интересует или спросит у друга в зале и он ему покажет и он запустит код и проверит - но тогда код уже будет цельный!

--

поэтому и можно хотябы в блендере показывать то о чем вы говорите или на движке каком-то, где там что стопорит по времени, есть даже проф движок g3d было бы желание показать только о чем вы пишите, к слову для прототипов у него есть бинд на луа

Боже, опять этот фонтан…

Sign up to leave a comment.

Articles