2014-01-06 1 views
14

Я реализую алгоритм (SpookyHash), который обрабатывает произвольные данные как 64-битные целые числа, путем литья указателя на (ulong*). (Это связано с тем, как работает SpookyHash, переписывание, чтобы не делать этого, не является жизнеспособным решением).Определение требований к выравниванию ЦП

Это означает, что он может завершить чтение 64-битных значений, которые не выровнены по 8-байтовым границам.

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

Поэтому у меня есть код для обнаружения невыровненных чтений и копирование кусков данных в 8-байтовые выровненные буферы, когда это необходимо, перед их работой.

Однако, моя собственная машина имеет Intel x86-64. Это допускает неглавные чтения достаточно хорошо, что дает гораздо более высокую производительность, если я просто игнорирую проблему выравнивания, равно как и x86. Он также позволяет использовать memcpy-like и memzero-подобные методы для работы в 64-байтовых кусках для другого повышения. Эти два улучшения производительности являются значительными, более чем достаточно для того, чтобы сделать такую ​​оптимизацию далеко не преждевременной.

So. У меня есть оптимизация, которая стоит делать на некоторых чипах (и, если на то пошло, вероятно, два чипа, которые, скорее всего, будут работать на этом коде), но будут фатальными или ухудшат производительность для других. Ясно, что идеальным является определение того, к какому случаю я имею дело.

Некоторые дополнительные требования:

  1. Это предназначено для кросс-платформенной библиотеки для всех систем, поддерживающих .NET или Mono. Поэтому ничего конкретного для данной ОС (например, P/Invoking для вызова ОС) не подходит, если только он не может безопасно деградировать перед вызовом, недоступным.

  2. Ложные негативы (идентификация чипа как небезопасного для оптимизации, когда это действительно безопасно) являются допустимыми, ложные срабатывания не являются.

  3. Дорогостоящие операции прекрасны, если они могут быть выполнены один раз, а затем результат кэшируется.

  4. В библиотеке уже используется небезопасный код, поэтому нет необходимости его избегать.

До сих пор у меня есть два подхода:

Первое являются инициализация моего флага с:

private static bool AttemptDetectAllowUnalignedRead() 
{ 
    switch(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")) 
    { 
    case "x86": case "AMD64": // Known to tolerate unaligned-reads well. 
     return true; 
    } 
    return false; // Not known to tolerate unaligned-reads well. 
} 

Другим является то, что с буфером копирования необходимо избегать выровненным чтения создаются с использованием stackalloc, а так как на x86 (в том числе AMD64 в 32-разрядном режиме), stackalloc В 64-битном типе иногда может возвращаться указатель, который выровнен по 4 байта, но не 8-байтовый, поэтому я могу тогда сказать, что выравнивание обходного пути не является eeded, и никогда не пытаться его снова:

if(!AllowUnalignedRead && length != 0 && (((long)message) & 7) != 0) // Need to avoid unaligned reads. 
{ 
    ulong* buf = stackalloc ulong[2 * NumVars]; // buffer to copy into. 
    if((7 & (long)buf) != 0) // Not 8-byte aligned, so clearly this was unnecessary. 
    { 
     AllowUnalignedRead = true; 
     Thread.MemoryBarrier(); //volatile write 

Это последний, хотя будет работать только на выполнении 32-разрядного (даже если выровненный 64-битых чтения допускаются, не хорошая реализация stackalloc не вынудит их на 64-битном процессор).Это также потенциально может дать ложный результат в том, что процессор может настаивать на 4-байтовом выравнивании, что будет иметь ту же проблему.

Любые идеи для усовершенствований или, еще лучше, подхода, который не дает ложных негативов, таких как два подхода выше?

+0

Определенно, ОБЯЗАТЕЛЬНО идти с первым. Правильный белый список - единственный способ пойти сюда (что, если ARMv9 эмулирует, но эмулирует неэффективно и т. Д.). Единственное изменение, которое я сделал бы, - это добавить белый список в app.config, чтобы вы могли проверять новые архитектуры и включать оптимизацию без переустановки/перераспределения. – Stu

+0

(На самом деле, это хуже - со вторым вы не просто зацепляете свой вагон с текущими реализациями ЦП, но и за реализацию Mono. Что делать, если в будущей версии они вдруг станут «полезными» «с вопросами выравнивания?) – Stu

+0

Хорошо, еще один, независимо от того, какой путь вы выберете: если вы кешируете свой результат между выполнением, чтобы избежать странных ситуаций на виртуальных машинах, не просто кешируйте флаг, а также кешируйте, на какой дуге вы получили результат. Затем при запуске проверьте, все ли вы работаете на одной арке. – Stu

ответ

4

Ну, вот мой собственный окончательный ответ для ответа. Хотя я отвечаю на свой собственный вопрос здесь, я обязан многим комментариям.

Комментарии Бен Фойгта и J Trana заставили меня кое-что понять. В то время как мой конкретный вопрос является логическим, общий вопрос не задан:

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

Как таковой, на самом деле нет ответа на вопрос: «Какие процессоры допускают невысокое считывание достаточно дешево?» но, скорее, «какие процессоры позволяют невысоплению читать дешево для моей текущей ситуации. Таким образом, любой полностью последовательный и надежный метод не просто невозможно, а как вопрос, не связанный с конкретным случаем, бессмысленным.

И как таковой , случаи с белым листингом, которые, как известно, достаточно хороши для кода под рукой, - единственный способ пойти.

Это Stu, хотя я должен добиться успеха с Mono on * nix до того, что у меня было с .NET и Mono в Windows. Дискуссия в приведенных выше комментариях привела мой подход к относительно простому, но разумно эффективному подходу (и если Stu отвечает на вопрос «Я думаю, вы должны основывать свой подход на том, код бежит безопасно ", я соглашусь с ним, потому что это была суть одного из h это предложения и ключ к тому, что я сделал).

Как и прежде, я сначала попробую проверить переменную окружения, которая обычно будет установлена ​​в Windows, и не будет установлена ​​ни на какую другую ОС.

Если это не удается, я пытаюсь запустить uname -p и проанализировать результаты. Это может произойти по нескольким причинам (не работает на * nix, не имея достаточных разрешений, работающих на одной из форм * nix, которая имеет команду uname, но не -p). В любом случае, я просто ем исключение, а затем попробую uname -m, который его более широко доступен, но имеет большее разнообразие меток для тех же чипов.

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

текущий код выглядит следующим образом:

[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", 
    Justification = "Many exceptions possible, all of them survivable.")] 
[ExcludeFromCodeCoverage] 
private static bool AttemptDetectAllowUnalignedRead() 
{ 
    switch(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")) 
    { 
    case "x86": 
    case "AMD64": // Known to tolerate unaligned-reads well. 
     return true; 
    } 
    // Analysis disable EmptyGeneralCatchClause 
    try 
    { 
    return FindAlignSafetyFromUname(); 
    } 
    catch 
    { 
    return false; 
    } 
} 
[SecuritySafeCritical] 
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", 
    Justification = "Many exceptions possible, all of them survivable.")] 
[ExcludeFromCodeCoverage] 
private static bool FindAlignSafetyFromUname() 
{ 
    var startInfo = new ProcessStartInfo("uname", "-p"); 
    startInfo.CreateNoWindow = true; 
    startInfo.ErrorDialog = false; 
    startInfo.LoadUserProfile = false; 
    startInfo.RedirectStandardOutput = true; 
    startInfo.UseShellExecute = false; 
    try 
    { 
    var proc = new Process(); 
    proc.StartInfo = startInfo; 
    proc.Start(); 
    using(var output = proc.StandardOutput) 
    { 
     string line = output.ReadLine(); 
     if(line != null) 
     { 
     string trimmed = line.Trim(); 
     if(trimmed.Length != 0) 
      switch(trimmed) 
      { 
      case "amd64": 
      case "i386": 
      case "x86_64": 
      case "x64": 
       return true; // Known to tolerate unaligned-reads well. 
      } 
     } 
    } 
    } 
    catch 
    { 
    // We don't care why we failed, as there are many possible reasons, and they all amount 
    // to our not having an answer. Just eat the exception. 
    } 
    startInfo.Arguments = "-m"; 
    try 
    { 
    var proc = new Process(); 
    proc.StartInfo = startInfo; 
    proc.Start(); 
    using(var output = proc.StandardOutput) 
    { 
     string line = output.ReadLine(); 
     if(line != null) 
     { 
     string trimmed = line.Trim(); 
     if(trimmed.Length != 0) 
      switch(trimmed) 
     { 
      case "amd64": 
      case "i386": 
      case "i686": 
      case "i686-64": 
      case "i86pc": 
      case "x86_64": 
      case "x64": 
      return true; // Known to tolerate unaligned-reads well. 
      default: 
      if(trimmed.Contains("i686") || trimmed.Contains("i386")) 
       return true; 
      return false; 
     } 
     } 
    } 
    } 
    catch 
    { 
    // Again, just eat the exception. 
    } 
    // Analysis restore EmptyGeneralCatchClause 
    return false; 
} 
+0

Dirtier, но гораздо более кратким, чем мой подход. Я бы придерживался этого. – Stu

+0

@Stu, да, я думаю, что тот факт, что попытка запустить процесс, я не уверен, идет против моих защитных инстинктов, поэтому я не думал об этом раньше; это кажется немного грязным. Ваш подход вызвал мысль, которая заставила меня подумать, поэтому большое спасибо за это. –

+0

@Stu, кстати, я также нашел хороший способ сделать это не заботой, когда речь идет о реализациях memcpy и memset, хотя я не добавил его в базу кода. В основном это сводится к использованию 'cpblk' и' initblk' из сборки, написанной в IL, и позволяя им иметь дело с ней соответствующим образом, за исключением x86, потому что 'cpblk' сосет на x86, но x86 - один из двух случаев Я знаю, что вам не нужны соображения о выравнивании. –

 Смежные вопросы

  • Нет связанных вопросов^_^