检测CPU对齐要求

Jon*_*nna 14 .net c# mono processor

我正在实现一个算法(SpookyHash),通过将指针转换为将任意数据视为64位整数(ulong*).(这是SpookyHash如何工作所固有的,重写不这样做不是一个可行的解决方案).

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

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

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

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

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

一些进一步要求:

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

  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.
}
Run Code Online (Sandbox Code Playgroud)

另一个是因为用于避免未对齐读取所需的缓冲区复制是使用创建的stackalloc,并且因为在x86(包括32位模式下的AMD64)中,stackalloc64位类型有时可能会返回一个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
Run Code Online (Sandbox Code Playgroud)

后者虽然只能用于32位执行(即使可以容忍未对齐的64位读取,但没有好的实现stackalloc会强制它们在64位处理器上).它也可能会产生误报,因为处理器可能会坚持4字节对齐,这会产生同样的问题.

任何改进的想法,或者更好的是,这种方法不会像上述两种方法那样产生假阴性?

Jon*_*nna 4

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

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

几乎所有现代处理器都会因未对齐读取而对性能造成影响,只是对于某些处理器而言,这种影响非常轻微,与避免它的成本相比可以忽略不计。

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

因此,将已知对于手头的代码来说足够好的白名单案例是唯一的出路。

不过,我在 *nix 上使用 Mono 取得的成功,直到在 Windows 上使用 .NET 和 Mono 取得的成功,都要归功于 Stu。上面评论中的讨论将我的思路带到了一种相对简单但相当有效的方法(如果 Stu 发布了一个答案“我认为你应该将你的方法建立在让特定于平台的代码安全运行的基础上”,我会接受它,因为这是他的建议之一的关键,也是我所做的事情的关键)。

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

如果失败,我尝试运行uname -p并解析结果。uname这可能会因多种原因而失败(不在 *nix 上运行、没有足够的权限、在具有命令但没有标志的 *nix 形式之一上运行-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;
}
Run Code Online (Sandbox Code Playgroud)