Итак, возникает вопрос: Что мне следует использовать, чтобы добиться лучшего времени выполнения?
temp := <expression>.
temp > 0
or
(temp := <expression>) > 0
В подобных случаях лучший способ прийти к заключению — спуститься на один шаг вниз по уровню абстракции. Другими словами, нам нужно лучше понимать, что происходит за кулисами.
Исполняемая часть CompiledMethod
представлена его байт-кодами. Когда мы сохраняем метод, мы компилируем его в серию низкоуровневых инструкций, чтобы виртуальная машина могла выполнять метод каждый раз, когда он вызывается. Итак, давайте взглянем на байт-коды каждого из приведенных выше случаев.
Поскольку <expression>
одинакова в обоих случаях, давайте резко уменьшим ее, чтобы устранить шум. Кроме того, давайте поместим наш код в метод, чтобы иметь CompiledMethod
для игры.
Object >> m
| temp |
temp := 1.
temp > 0
Теперь давайте посмотрим на CompiledMethod
и его суперклассы на наличие сообщения, которое показало бы нам байт-коды Object >> #m
. Селектор должен содержать байт-коды подслов, верно?
...
Вот оно #symbolicBytecodes
! Теперь давайте оценим (Object >> #m) symbolicBytecodes
, чтобы получить:
pushConstant: 1
popIntoTemp: 0
pushTemp: 0
pushConstant: 0
send: >
pop
returnSelf
Кстати, обратите внимание, как наша переменная temp
была переименована в Temp: 0
на языке байт-кодов.
Теперь повторите с другим и получите:
pushConstant: 1
storeIntoTemp: 0
pushConstant: 0
send: >
pop
returnSelf
Разница в том,
popIntoTemp: 0
pushTemp: 0
против
storeIntoTemp: 0
Это показывает, что в обоих случаях temp
читается из стека по-разному. В первом случае результат нашего <expression>
извлекается из стека выполнения в temp
, а затем снова помещается temp
для восстановления стека. За pop
следует push
того же самого. Вместо этого во втором случае push
или pop
не происходит, а temp
просто считывается из стека.
Таким образом, вывод таков, что в первом случае мы будем генерировать две инструкции отмены pop
, за которыми следует push
.
Это также объясняет, почему разницу так трудно измерить: инструкции push
и pop
имеют прямой перевод в машинный код, и ЦП будет выполнять их очень быстро.
Обратите внимание, однако, что ничто не мешает компилятору автоматически оптимизировать код и понять, что на самом деле pop + push
эквивалентно storeInto
. С такой оптимизацией оба фрагмента Smalltalk приведут к одному и тому же машинному коду.
Теперь вы должны решить, какую форму вы предпочитаете. На мой взгляд, такое решение должно учитывать только тот стиль программирования, который вам больше нравится. Принятие во внимание времени выполнения не имеет значения, потому что разница минимальна и может быть легко сведена к нулю путем реализации оптимизации, которую мы только что обсуждали. Между прочим, это было бы отличным упражнением для тех, кто хочет понять низкоуровневые области беспрецедентного языка Smalltalk.
person
Leandro Caniglia
schedule
07.07.2018
10000/2 < 0
ложно?). Первый тест заключается в том, масштабируется ли время линейно с количеством повторений, если у вас есть это внутри цикла, который выполняет это многократно. Во-вторых, масштабируется ли вообще время с помощьюvar
. IDK, как компилируется и интерпретируется реализация, но с постоянным аргументом времени компиляции он может просто оптимизировать доacc += constant
. - person Peter Cordes   schedule 07.07.2018