2015-11-18 3 views
6

У меня есть следующий C# код пытается бенчмарка в режиме выпуска:Медленное исполнение под 64 бит. Возможная ошибка RyuJIT?

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication54 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     int counter = 0; 
     var sw = new Stopwatch(); 
     unchecked 
     { 
      int sum = 0; 
      while (true) 
      { 
       try 
       { 
        if (counter > 20) 
         throw new Exception("exception"); 
       } 
       catch 
       { 
       } 

       sw.Restart(); 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed); 
      } 

     } 
    } 
} 
} 

Я на 64-битной машине и VS 2015 установлена. Когда я запускаю код под 32-битным, он запускает каждую итерацию около 0,6 секунды, печатается на консоль. Когда я запускаю его под 64-битным, продолжительность каждой итерации просто переходит на 4 секунды! Я попробовал образец кода на компьютере моих коллег, на котором установлен только VS 2013. Там как 32-битные, так и 64-разрядные версии работают около 0,6 секунды.

В дополнение к этому, если мы просто удалим блок захвата try, он также работает в 0,6 секунды с VS 2015 в 64-разрядной версии.

Это выглядит как серьезная регрессия RyuJIT, когда есть блок catch try. Я прав ?

+0

Ваш компьютер - супер гений! для меня это занимает около 10 секунд на каждой итерации: (и никакой разницы здесь. И 32bit, и 64bit дают такие же результаты. –

+0

@ M.kazem. Я не думаю, что это возможно. Мой компьютер - Surface Pro 3 i7 с U я уверен, что вы запускаете его в режиме Release и Start без отладки? BTW Я пытался использовать 4 разных компьютера. –

+0

Нет, нет. У меня это получилось, потому что вы включили опцию 'Optimize code 'в свойствах решения. Теперь я получаю' 1.3s' для 32bit и '3.9s' для 64 бит. –

ответ

11

Настольная маркировка - это изобразительное искусство. Внесите небольшие изменения в код:

Console.WriteLine("{0}", sw.Elapsed, sum); 

И теперь вы увидите, что разница исчезнет. Или, говоря иначе, версия x86 теперь так же медленна, как и x64-код. Вы, наверное, можно понять, что RyuJIT не делать то, что наследство джиттера сделал из этого незначительные изменения, не устраняет ненужную

sum += i; 

Что-то вы можете видеть, когда вы посмотрите на сгенерированный машинный код с Debug> Windows> Разборка. Это действительно причуда в RyuJIT. Его уничтожение мертвого кода не так тщательно, как унаследованный джиттер. В противном случае, не совсем без причины, Microsoft переписала джиттер x64 из-за ошибок, которые он не мог легко исправить. Один из них был довольно неприятным вопросом с оптимизатором, он не имел верхней границы времени, затраченного на оптимизацию метода. Вызывая довольно плохое поведение методов с очень большими телами, он может находиться в лесу в течение десятков миллисекунд и вызывать заметные паузы при выполнении.

Вызывает это ошибка, но не очень. Напишите здравый код, и дрожание вас не разочарует. Оптимизация делает всегда на обычном месте, между ушами программиста.

0

После небольшого тестирования у меня есть интересные результаты. Мое тестирование вращалось вокруг блока try catch. Как указывал ОП, если вы удалите этот блок, время выполнения будет таким же. Я сузил это немного дальше и пришел к выводу, что из-за counter переменной в if в блоке try.

Позволяет удалить излишний throw:

   try 
       { 
        if (counter== 0) { } 
       } 
       catch 
       { 
       } 

Вы получите те же результаты, с этим кодом, как вы сделали с оригинальным кодом.

Позволяет счетчик изменений, чтобы быть фактическое значение INT:

   try 
       { 
        if (1 == 0) { } 
       } 
       catch 
       { 
       } 

С помощью этого кода, 64-битная версия уменьшилась во время выполнения от 4 секунд до примерно 1,7 секунды. Тем не менее, это вдвое больше, чем 32-битная версия. Однако я думал, что это интересно.К сожалению, после моего быстрого поиска в Google у меня нет причины, но я буду копать немного больше и обновить этот ответ, если узнаю, почему это происходит.

Что касается оставшейся секунды, что мы хотели бы, чтобы сбрить 64 битную версию, я могу видеть, что это вниз приращение sum по i в вашем for цикла. Позволяет изменить это так, чтобы sum не превышает его пределы:

  for (int i = 0; i < int.MaxValue; i++) 
      { 
       sum ++; 
      } 

Это изменение (наряду с изменением try блока) позволит сократить время выполнения приложения 64 бит до 0,7 секунд. Мое рассуждение о 1-секундной разнице во времени связано с искусственным способом, которым 64-разрядная версия должна обрабатывать int, который, естественно, 32 бита.

В 32-разрядной версии 32 бит выделен для Int32 (sum). Когда sum переходит выше своих границ, это легко определить.

В 64-разрядной версии 64 бит выделен для Int32 (sum). Когда сумма идет выше ее границ, должен быть механизм для обнаружения этого, что может привести к замедлению. Возможно, даже операция добавления sum & i занимает больше времени из-за увеличения выделенных выделенных битов.

Я теоретизирую здесь; так что не принимайте это как Евангелие. Я просто подумал, что опубликую свои выводы. Я уверен, что кто-то еще сможет пролить свет на проблему, которую я нашел.

-

Update

ответ @HansPassant «s отметил, что sum += i; линия может быть устранено, поскольку это считается ненужным, что имеет смысл, sum не используется вне цикла for , После того, как он представил значение суммы вне цикла for, мы заметили, что версия x86 была такой же медленной, как и версия x64. Поэтому я решил немного проверить. Позволяет изменить цикл и печати на следующее:

   int x = 0; 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
        x = sum; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed + " " + x); 

Вы можете видеть, что я ввел новый int x, который в настоящее время присваивается значение sum в петле for. Это значение x не выписывается на консоль. sum не покидает петлю for. Это, верьте или нет, фактически сокращает время выполнения для x64 до 0,7 секунды. Тем не менее, версия x86 сканируется до 1,4 секунд.

+0

Не имеет значения, превышает ли 'sum' над его границами - он работает в неконтролируемом контексте, поэтому переполнение игнорируется - если код вообще запущен. Как отмечает Ханс, поскольку переменная 'sum' не считывается после этого цикла, это возможно * (x86, старше x64 JIT), чтобы полностью исключить цикл как оптимизацию. –

+0

@Damien_The_Unbeliever Вы действительно подняли очень интересный момент. Позвольте мне обновить свой ответ своими выводами. –

+0

@Damien_The_Unbeliever Я не думаю, что факт, что это непроверено, на самом деле имеет значение. Это означает, что он определенно не собирается бросать исключение OverflowException, когда сумма достигает своих границ. (В отличие от того, если вы изменили его на отмеченный). В любом случае, по-прежнему необходимо выполнить искусственную проверку границ в 64 бит, потому что int имеет 64 бита, выделенные для него. В теории, я думаю. Опять же, я не специалист в этих вопросах, это меня просто заинтересовало. –