Почему порядок, в котором методы С# в .NET 4.0 компилируются вовремя, влияет на то, как быстро они выполняются? Например, рассмотрим два эквивалентных метода:
public static void SingleLineTest()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
count += i % 16 == 0 ? 1 : 0;
}
stopwatch.Stop();
Console.WriteLine("Single-line test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
}
public static void MultiLineTest()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
var isMultipleOf16 = i % 16 == 0;
count += isMultipleOf16 ? 1 : 0;
}
stopwatch.Stop();
Console.WriteLine("Multi-line test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
}
Единственное различие заключается в введении локальной переменной, которая влияет на генерируемый код сборки и производительность цикла. Почему именно так возникает вопрос.
Возможно, еще страннее то, что на x86 (но не на x64) порядок, который вызывают методы, оказывает примерно 20% влияние на производительность. Вызовите методы, подобные этому...
static void Main()
{
SingleLineTest();
MultiLineTest();
}
... и SingleLineTest
быстрее. (Скомпилируйте с использованием конфигурации релиза x86, убедитесь, что параметр "Оптимизировать код" включен, и запустите тест из-за внешнего VS2010.) Но измените порядок...
static void Main()
{
MultiLineTest();
SingleLineTest();
}
... и оба метода берут одно и то же время (почти, но не совсем, пока MultiLineTest
до). (При выполнении этого теста полезно добавить дополнительные вызовы SingleLineTest
и MultiLineTest
, чтобы получить дополнительные образцы. Сколько и какой порядок не имеет значения, за исключением того, какой метод вызывается первым.)
Наконец, чтобы продемонстрировать, что порядок JIT важен, сначала оставьте MultiLineTest
, но сначала запустите SingleLineTest
в JITed...
static void Main()
{
RuntimeHelpers.PrepareMethod(typeof(Program).GetMethod("SingleLineTest").MethodHandle);
MultiLineTest();
SingleLineTest();
}
Теперь SingleLineTest
выполняется быстрее.
Если вы отключите "Запретить оптимизацию JIT при загрузке модуля" в VS2010, вы можете поместить контрольную точку в SingleLineTest
и увидеть, что код сборки в цикле одинаковый независимо от порядка JIT; однако код сборки в начале метода меняется. Но как это имеет значение, когда основная часть времени проводится в цикле, вызывает недоумение.
A пример проекта, демонстрирующий это поведение находится в github.
Непонятно, как это поведение влияет на приложения реального мира. Одна из проблем заключается в том, что она может привести к изменчивости производительности в летучем режиме, в зависимости от методов порядка, которые будут сначала вызваны. Проблем такого рода было бы трудно обнаружить с помощью профилировщика. После того, как вы нашли горячие точки и оптимизировали их алгоритмы, было бы трудно узнать без большой догадки и проверить, возможно ли раннее ускорение с помощью методов JITing.
Обновление: См. также запись Microsoft Connect для этой проблемы.