【问题标题】:Detecting CPU alignment requirements检测 CPU 对齐要求
【发布时间】:2014-01-06 13:27:28
【问题描述】:

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

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

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

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

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

所以。我有一个非常值得在某些芯片上进行的优化(就此而言,可能是最有可能在其上运行此代码的两个芯片),但对其他芯片来说可能是致命的或性能更差。显然,理想的情况是检测我正在处理哪种情况。

一些进一步的要求:

  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(包括 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

尽管后者仅适用于 32 位执行(即使允许未对齐的 64 位读取,stackalloc 的良好实现也不会强制它们在 64 位处理器上执行)。它还可能会产生误报,因为处理器可能会坚持 4 字节对齐,这会产生同样的问题。

有什么改进的想法,或者更好的方法,像上面两种方法一样不会产生误报吗?

【问题讨论】:

  • 绝对,绝对选择第一个。一个适当的白名单是唯一的方法(如果 ARMv9 模拟但模拟效率低下,等等)。我要做的唯一更改是将白名单放在 app.config 中,这样您就可以验证新架构并启用优化,而无需重新构建/重新部署。
  • (实际上,情况更糟——使用第二个,您不仅将您的马车搭上了当前的 CPU 实现,而且还搭上了 Mono 的实现。如果他们在未来的版本中,所有突然决定对对齐问题“有帮助”?)
  • 好吧,再来一个,不管你选择哪条路径:如果你在执行之间缓存你的结果,为了防止虚拟机上出现奇怪的情况,不要只缓存标志,还要缓存你得到的结果在。然后在启动时检查您是否仍在同一个拱门上运行。
  • @Stu,第一个运行良好,第二个有我在编辑中注意到的逻辑缺陷,这并不意味着处理器不能进行 4 字节对齐,但如果不是就会失败在 4 字节上。不过,我不喜欢永久缓存的想法,而不是内存中的静态变量。因为它是一个库,所以如果我不执行任何其他与文件相关的任务,IMO,我不应该要求文件写入权限(即使在某些沙箱中)。
  • @Stu,第一个的一个大风险是 x86 或 AMD64 的出现不能容忍这种对齐问题。 PowerPC 芯片 IIRC 发生了这种情况,它们的对齐方式比以前更严格了。

标签: c# .net mono processor


【解决方案1】:

嗯,这是我自己的最终答案。虽然我在这里回答我自己的问题,但我欠 cmets 很多。

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

几乎所有现代处理器都会对未对齐读取产生性能影响,只是有些影响很小,与避免它的成本相比微不足道。

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

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

这要归功于 Stu,尽管我应该在 *nix 上使用 Mono 获得成功,这与在 Windows 上使用 .NET 和 Mono 一样。上面 cmets 中的讨论将我的思路引向了一个相对简单但相当有效的方法(如果 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;
}

【讨论】:

  • 更脏,但比我的方法简洁得多。我会坚持这个。
  • @Stu,是的,我认为尝试运行一个我不确定的进程这一事实违背了我的防御本能,这就是为什么我没有早点想到它的原因;它似乎有点脏。你的方法引发了我的思考,非常感谢你。
  • @Stu,顺便说一句,我还找到了一种很好的方法,可以让我在 memcpy 和 memset 实现中不必担心,尽管我还没有将它添加到代码库中。基本上它归结为使用用 IL 编写的程序集中的 cpblkinitblk,并让他们适当地处理它,除了在 x86 上,因为 cpblk 在 x86 上很烂,但 x86 很好地是这两种情况之一我知道不需要对齐考虑。
猜你喜欢
  • 2012-03-09
  • 2018-01-09
  • 2014-09-12
  • 2017-04-23
  • 1970-01-01
  • 2011-03-02
  • 2018-03-12
  • 2018-07-29
相关资源
最近更新 更多