На скорость кода в основном влияют низкоуровневые оптимизации архитектуры компьютера, как с точки зрения процессора, так и других оптимизаций.
Существует множество факторов, влияющих на скорость кода, и обычно это вопросы низкого уровня, которые автоматически обрабатываются компилятором, но это может ускорить ваш код, если вы знаете, что делаете.
Прежде всего, очевидно, Word Size. 64-битные машины имеют больший размер слова (да, здесь больше обычно означает лучше), так что большинство операций может выполняться быстрее, например, операции с двойной точностью (где double обычно означает 2 * 32 бита). 64-битная архитектура также выигрывает от большей шины данных, которая обеспечивает более высокую скорость передачи данных.
Во-вторых, трубопровод также важен. Основные инструкции могут быть классифицированы в различных состояниях или фазах, так что, например, инструкции обычно делятся на:
- Выборка: инструкция читается из кэша инструкций
- Декодирование: инструкция расшифровывается как интерпретированная, чтобы увидеть, что мы должны сделать.
- Выполнить: инструкция выполняется (обычно это означает перенос операций в АЛУ)
- Доступ к памяти: если инструкция должна получить доступ к памяти (например, загрузить значение реестра из кэша данных), она выполняется здесь.
- Обратная запись: значения записываются обратно в регистр назначения.
Теперь конвейер позволяет процессору разделять команды на эти фазы и выполнять их одновременно, так что, выполняя одну инструкцию, он также декодирует следующую и извлекает последующую.
Некоторые инструкции имеют зависимости. Если я добавляю в регистры вместе, для выполнения фазы инструкции add потребуются значения, прежде чем они действительно будут восстановлены из памяти. Зная структуру конвейера, компилятор может переупорядочить инструкции по сборке, чтобы обеспечить достаточное «расстояние» между нагрузками и надстройкой, чтобы процессору не пришлось ждать.
Другая оптимизация ЦП будет суперскалярной, в которой используются, например, избыточные ALU, так что две команды добавления могут выполняться одновременно. Опять же, точно зная архитектуру, вы можете оптимизировать порядок команд, чтобы воспользоваться преимуществами. Например, если компилятор обнаруживает, что в коде не существует зависимостей, он может переставить нагрузки и арифметику так, чтобы арифметика была перенесена на более позднее место, где все данные были доступны, а затем выполняла 4 операции одновременно.
Это в основном используется компиляторами.
Что может быть полезно при разработке вашего приложения и что действительно может повысить скорость кода, так это знание политик и организации кэша. Наиболее типичный пример приведен для неправильно упорядоченного доступа к двойному массиву в цикле:
// Make an array, in memory this is represented as a 1.000.000 contiguous bytes
byte[][] array1 = new byte[1000, 1000];
byte[][] array2 = new byte[1000, 1000;
// Add the array items
for (int j = 0; j < 1000; i++)
for (int i = 0; i < 1000; j++)
array1[i,j] = array1[i,j] + array2[i,j]
Посмотрим, что здесь происходит.
array1 [0,0] заносится в кеш. Поскольку кеш работает в блоках, вы получаете первые 1000 байтов в кеш, поэтому кеш содержит массив1 [0,0] и массив1 [0,999].
array2 [0,0] добавлено в кеш. Снова блокирует, так что у вас есть массив2 [0,0] в массив2 [0,999].
На следующем шаге мы получаем доступ к массиву 1 [1,0], которого нет в кэше, и к массиву 2 [1,0], поэтому мы переносим их из памяти в кэш. Теперь, если мы предположим, что у нас очень маленький размер кэша, из-за этого массив2 [0 ... 999] будет удален из кэша ... и так далее. Поэтому, когда мы обращаемся к array2 [0,1], он больше не будет в кеше. Кэш не будет полезен для array2 или array1.
Если мы изменим порядок обращения к памяти:
for (int i = 0; i < 1000; i++)
for (int j = 0; j < 1000; j++)
array1[i,j] = array1[i,j] + array2[j,i]
Нет необходимости извлекать память из кэша, и программа будет работать значительно быстрее.
Это все наивные, академические примеры, если вы действительно хотите или должны изучать компьютерную архитектуру, вам нужно очень глубокое знание специфики архитектуры, но опять же, это будет полезно только при программировании компиляторов. Тем не менее, базовые знания о кеше и базовом низкоуровневом процессоре могут помочь вам улучшить вашу скорость.
Например, такие знания могут иметь огромное значение в криптографическом программировании, где вам приходится обрабатывать очень большие числа (например, в 1024 битах), чтобы правильное представление могло улучшить нижнюю математику, которая должна быть выполнена ...