我正在实现一种算法(SpookyHash),它通过将指针转换为(ulong*)
将任意数据视为64位整数。 (这是SpookyHash工作原理所固有的,重写不这样做是不可行的。)
这意味着它最终可能会读取未在8字节边界上对齐的64位值。
在某些CPU上,这很好用。在某些情况下,它会非常缓慢。在其他情况下,它会导致错误(异常或不正确的结果)。
因此,我有代码来检测未对齐的读取,并在必要时将数据块复制到8字节对齐的缓冲区,然后再进行处理。
但是,我自己的机器有Intel x86-64。这样可以很好地容忍未对齐的读取,如果我只是忽略对齐问题,它会提供更快的性能,就像x86一样。它还允许memcpy
- 类似和memzero
类似的方法处理64字节块以进行另一次提升。这两项性能改进是相当可观的,足以促使这种优化远未过早。
因此。我有一个非常值得在某些芯片上进行的优化(对于这个问题,可能是两个最有可能运行此代码的芯片),但是会致命或者在其他芯片上表现更差。显然,理想的是检测我正在处理的是哪种情况。
一些进一步的要求:
这是一个支持.NET或Mono的所有系统的跨平台库。因此,特定于某个操作系统的任何内容(例如P /调用OS调用)都是不合适的,除非它可以在呼叫不可用时安全地降级。
假阴性(确定芯片在实际上是安全的时候对于优化是不安全的)是可以容忍的,误报不是。
昂贵的操作很好,只要它们可以完成一次,然后缓存结果。
该库已经使用了不安全的代码,所以没有必要避免这种情况。
到目前为止,我有两种方法:
首先是用以下内容初始化我的旗帜:
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字节对齐,这会产生同样的问题。
任何改进的想法,或者更好的是,这种方法不会像上述两种方法那样产生误报?
答案 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;
}