Ш>>>Данная функция нормально себя ведет, если i и j указывают на разные переменные и в этом случае параллелизация инкрементов -- хорошая идея. Ш>>>Но вот если они указывают на одну переменную...
F>>Ну так здесь неопределенное поведение в случае, когда i и j указывают на одну переменную.
Ш>Ага. Вопрос только -- зачем компилятору генерировать "плохой" код.
Потому что "плохой" быстрее, ибо полнее использует параллелизацию операций.
F>>Кстати, в студенчестве мы проходили среди прочего конвейерные (команда может быть выбрана на исполнение до завершения исполнения предыдущей команды) и суперскалярные (несколько одновременно работающих конвейеров) архитектуры процессоров.
F>>Помню что некоторые процессоры с помощью какой-то магии умеют определять истинные зависимости по данным такого рода (чтение после записи) и в качестве контрмеры приостанавливают конвейер(ы), пока не будет завершено выполнение первой команды.
Ш>Не некоторые. Все современные процессоры -- конвейерные.
Ш>И все они осуществляют приостановку конвейера при возникновении зависимости между входом и выходом. Иначе такой процессор невозможно было бы использовать.
Ты уверен что это обязательное условие и что все современные процы это могут? Судя по приведенному dupaid'ом asm-листингу, точки, где нужно ожидать окончания выполнения предыдущих команд, определяются не процессором самостоятельно, а компилятором. Или я не так понял листинг?
Ш>Магии тут нет никакой -- просто схемотехника.
Схемотехника для меня — магия
Ш>Если интересно и есть свободное время, можешь глянуть вот этот документ. Ш>Особенно главу Pipeline.
Никуда не спешу, так что попробую
F>>Может Itanium тоже так умеет и в ситуации, когда i и j указывают на один объект, инкременты будут выполнены последовательно?
Ш>Зависит от того, что сгенерирует компилятор.
Ш>Пример (условный). В одной строчке -- параллельно выполняющиеся команды.
Ш>i = [SP+offset i] Ш>j = [SP+offset j]
Ш>R1 <- i R2 <- j Ш>R3 <- [R1] R4 <- [R2] Ш>R3++ R4++ Ш>R0 <- R3 + R4 // возвращаем значение в R0 Ш>i <- R3 j <- R4
Ш>В последнеё строчке конфликт доступа к памяти. Проц как-то разрешит его, но это не сделает результат работы функции осмысленным.
Ш>Пусть i и j указывают на одну ячейку со значением 0. Тогда функция вернет 2. При последоватнльном же выполнении, т.е.
Ш>
Ш>int i_res=++(*i);
Ш>int j_res=++(*j);
Ш>return i_res+j_res; // Всё что мы сделали -- разбили выражение на два подвыражения.
Ш>
Ш>функция вернула бы 3.
Это понятно. Я имел ввиду, что может быть процу удастся последовательно выполнить инкременты даже для первого (параллельного) варианта. Но теперь думаю что это вряд ли.
Afaik, компилятор C++ вправе действовать по первому сценарию, т.е. генерировать более быстрый код. Перепишем второй вариант на псевдоасме, получится менее эффективно:
i = [SP+offset i]
j = [SP+offset j]
R1 <- i R2 <- j
R3 <- [R1]
R3++
i <- R3
R4 <- [R2]
R4++
j <- R4
R0 <- R3 + R4 // возвращаем значение в R0
Здравствуйте, folk, Вы писали:
Ш>>Если интересно и есть свободное время, можешь глянуть вот этот документ. Ш>>Особенно главу Pipeline.
Прочитал Pipeline, поправь если чего не так понял.
Конфликты доступа к памяти там разрешаются с помощью приостановки конвейера. Но такой конфликт, насколько я понял, происходит при доступе не только к одному адресу, но вообще при доступе к одному блоку памяти, т.е. разрешается именно структурный конфликт, а не зависимость по данным. А в случае чтения после записи по одному и тому же адресу происходит чтение значения из внутреннего буфера — это уже оптимальное разрешение зависимости по данным.
Но этот DSP не является хорошим примером. Насколько я понял, адрес операнда является частью команды, что позволяет легко засекать возникновение зависимостей.
А в контексте этого разговора скорее интересны механизмы определения зависимостей, характерных для RISC-архитектур. Т.е. когда операции загрузки/выгрузки регистров в память и операции над данными в регистрах разделены. На вскидку — можно для каждого регистра хранить соответствующий адрес памяти (если такой есть) для определения зависимостей.
Здравствуйте, folk, Вы писали:
Ш>>И все они осуществляют приостановку конвейера при возникновении зависимости между входом и выходом. Иначе такой процессор невозможно было бы использовать.
F>Ты уверен что это обязательное условие и что все современные процы это могут?
Уверен. Просто потому, что разрешить конфликт чтение-запись в большинстве случаев можно только уже в процессе исполнения. Ибо только в этот момент становятся известны адреса.
Альтернатива -- вставлять nop ы при первых подозрениях. Но в этом случае процессор будет работать фактически на сокращённой частоте -- а тогда и конвеер не нужен, теряется смысл всех нагромождений.
F>Судя по приведенному dupaid'ом asm-листингу, точки, где нужно ожидать окончания выполнения предыдущих команд, определяются не процессором самостоятельно, а компилятором. Или я не так понял листинг?
Не могу сказать, я с Itanium ом не знаком.
Ш>>Магии тут нет никакой -- просто схемотехника.
F>Схемотехника для меня — магия
Да брось -- нет там никакой магии. И вообще -- каждому программеру по осцилографу на стол!
Здравствуйте, folk, Вы писали:
F>Здравствуйте, folk, Вы писали:
Ш>>>Если интересно и есть свободное время, можешь глянуть вот этот документ. Ш>>>Особенно главу Pipeline.
F>Прочитал Pipeline, поправь если чего не так понял. F>Конфликты доступа к памяти там разрешаются с помощью приостановки конвейера.
Да.
F> Но такой конфликт, насколько я понял, происходит при доступе не только к одному адресу, но вообще при доступе к одному блоку памяти, т.е. разрешается именно структурный конфликт, а не зависимость по данным.
Это и есть зависимость по данным. Данные лежать в банках памяти. Доступ к банку, вообще говоря, осуществляется в монопольном режиме.
F>А в случае чтения после записи по одному и тому же адресу происходит чтение значения из внутреннего буфера — это уже оптимальное разрешение зависимости по данным.
Нет. Происходит задержка конвейера.
For example:
STL A, *AR1+ ; AR1 and AR3 points at the same SARAM block.
LD *AR3, B ; This instruction takes 1 additional cycle due to a memory access conflict.
F>Но этот DSP не является хорошим примером. F>Насколько я понял, адрес операнда является частью команды, что позволяет легко засекать возникновение зависимостей.
>>> Пример я уже приводил f(i++), f(i++) все должно вычисляться с лева-на-право (это не нарушено), но аргументы следую твоей логике получаем такую последовательность i++ i++ f() f(). Где фундаментальное отличие. > > ПК>Фундаментальное отличие в том, что в случае наличия запятой, стандарт явно определяет порядок вычислений: > ПК>
All side effects (1.9) of the left expression, except for the destruction of temporaries (12.2), are performed before the evaluation of the right expression.
> ПК>В примере без запятой какого-либо определенного порядка нет. > > А порядок и не нарушается, в начале вычисляется левый операнд (только в него подмешивается правый операнд ) все его побочные действия завершаются, потом довычисляется правый, и все его побочные действия завершаются. Так что порядок не нарушился в соответствии с этим пунктом. Мы просто начали вычислять второй операнд до точки следования.
Как видишь, в случае наличия запятой, это противоречит процитированной части стандарта, согласно которой, никакого подмешивания правого операнда в левый быть не может.
Posted via RSDN NNTP Server 1.9 alpha
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Ivan A. Kosarev, Вы писали:
IAK>Еще вспоминаю давнюю дискуссию между Павлом Кузнецовым и dupamid. Павел говорил о неатомарности присваиваний. Так вот кроме того, что об атомарности (кроме sig_atomic_t и смежного с ним) в стандартах ничего не говорится, существует значительная масса кода в духе IAK>link = link -> next; IAK>которая никакой неатомарности не потерпит.
Производители процессоров всеми силами стремятся избежать неопределенности, или того что запись/чтение в одну ячейку могут вызвать серьезные проблемы. Так как существует огромное количество кода с такими "нарушениями". Поэтому, например, Itanium поддерживает явный параллелизм на уровне инструкций, но работа с памятью синхранизована на аппаратном уровне, чтобы не было таких проблем, так как софтверно их трудно разрешить. Инструкции работы с памятью выполняются в том порядке как записаны, если области перекрывающиеся — потеря производительности, но работает все как ожидалось.