检测CPU对齐要求

时间:2014-01-06 13:27:28

标签: c# .net mono processor

我正在实现一种算法(SpookyHash),它通过将指针转换为(ulong*)将任意数据视为64位整数。 (这是SpookyHash工作原理所固有的,重写不这样做是不可行的。)

这意味着它最终可能会读取未在8字节边界上对齐的64位值。

在某些CPU上,这很好用。在某些情况下,它会非常缓慢。在其他情况下,它会导致错误(异常或不正确的结果)。

因此,我有代码来检测未对齐的读取,并在必要时将数据块复制到8字节对齐的缓冲区,然后再进行处理。

但是,我自己的机器有Intel x86-64。这样可以很好地容忍未对齐的读取,如果我只是忽略对齐问题,它会提供更快的性能,就像x86一样。它还允许memcpy - 类似和memzero类似的方法处理64字节块以进行另一次提升。这两项性能改进是相当可观的,足以促使这种优化远未过早。

因此。我有一个非常值得在某些芯片上进行的优化(对于这个问题,可能是两个最有可能运行此代码的芯片),但是会致命或者在其他芯片上表现更差。显然,理想的是检测我正在处理的是哪种情况。

一些进一步的要求:

  1. 这是一个支持.NET或Mono的所有系统的跨平台库。因此,特定于某个操作系统的任何内容(例如P /调用OS调用)都是不合适的,除非它可以在呼叫不可用时安全地降级。

  2. 假阴性(确定芯片在实际上是安全的时候对于优化是不安全的)是可以容忍的,误报不是。

  3. 昂贵的操作很好,只要它们可以完成一次,然后缓存结果。

  4. 该库已经使用了不安全的代码,所以没有必要避免这种情况。

  5. 到目前为止,我有两种方法:

    首先是用以下内容初始化我的旗帜:

    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上(包括32位模式下的AMD64),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 个答案:

答案 0 :(得分:4)

嗯,这是我自己最后的答案。当我在这里回答我自己的问题时,我非常感谢这些评论。

Ben Voigt和J Trana的评论让我意识到了一些事情。虽然我的具体问题是布尔问题,但一般问题不是:

几乎所有的现代处理器都会因未对齐的读取而受到性能影响,只是因为有些内容的触发是轻微的,与避免它的成本相比微不足道。

因此,问题确实没有答案,“哪些处理器允许未对齐的读取足够便宜?”但是,“哪些处理器允许对我目前的情况进行廉价读取。因此,任何完全一致和可靠的方法不仅是不可能的,而且作为一个与特定情况无关的问题,毫无意义。

因此,已知对于手头的代码足够好的白名单案例是唯一可行的方法。

这是Stu虽然我应该努力在* nix上使用Mono获得我的成功,直到我在Windows上使用.NET和Mono。上面评论中的讨论使我的思路变得相对简单但相当有效,并且如果Stu发布了一个答案,“我认为你的方法应该基于平台特定的代码安全运行”,我会接受它,因为这是他的一个建议的关键,也是我所做的关键。

和以前一样,我首先尝试检查一般在Windows中设置的环境变量,而不是在任何其他操作系统上设置。

如果失败,我会尝试运行uname -p并解析结果。这可能由于各种原因而失败(没有在* nix上运行,没有足够的权限,在具有uname命令但没有-p标志的* nix形式之一上运行)。除了任何例外,我只是吃异常,然后尝试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;
}