Я реализую алгоритм (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-байтовом выравнивании, что будет иметь одинаковую проблему.
Любые идеи для улучшений или, еще лучше, подхода, который не дает ложных негативов, как два вышеупомянутых подхода?