Определение требований к выравниванию ЦП

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

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

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

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

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

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

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

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

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

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

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

До сих пор у меня было два подхода:

Сначала нужно инициализировать мой флаг:

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 байт, я могу тогда сказать, что обходное решение выравнивания не требуется, и никогда не пытайтесь повторить его:

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-байтовом выравнивании, что будет иметь одинаковую проблему.

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

Ответ 1

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

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

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

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

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

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

Как и прежде, я сначала попробую проверить переменную среды, которая обычно будет установлена ​​в 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;
}