Возможно, лучший аргумент против написания «оптимизированной» код, как это тот факт, что это одно заявление, и вы получили это неправильно (будь то предполагается, C# или C++, но по разным причинам в каждом случае). Это не SWAP две переменные:
a ^= b ^= a ^= b;
Это делает:
a ^= b;
b ^= a;
a ^= b;
Для остальной части этого ответа, я буду считать, что вы имели в виду второе.
Я не хочу, чтобы сделать копии всех структур в случае, если они очень большие. Так что я хочу сделать эквивалент этой C++ сниппета:
template <typename T>
void PointerSwap<T> (T* a, T* b) {
T* temp = a;
a = b;
b = temp;
}
[...] Неужели нет способа просто используя интегральную ссылку типа (так называемый «адрес памяти» в C++) в качестве параметра?
Ваш C++ фрагмент также не может поменять два внешних переменных. Вместо этого он просто меняет значения двух локальных переменных и это не работает в отношении внешнего кода.
В C#, ref
был бы самым простым способом добиться того же, что вы пытались здесь, и это не позволило бы вам совершить эту ошибку в первую очередь.
В любом случае мне непонятно, почему вы думаете, что использование таких указателей каким-то образом позволит вам менять значения без копирования чего-либо в любой момент. Даже в вашем фрагменте вы не изменяете, где находятся переменные, вы все еще перемещаете данные, которые они содержат.
Есть ли лучший способ проверить, является ли два элемента XORable?
Существует даже не способ проверить, что (за исключением полностью отказаться от производительности и с помощью отражения), потому что нет C# ограничение, которое описывает типы с operator^
(или типов с любым operator
, для это вопрос). То, что вы проверили, является ли тип Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, IntPtr
, UIntPtr
, Char
, Double
, Single
или Decimal
. Вы пропустите любой другой тип, который имеет operator^
. Вы также будете включать такие типы, как Single
(float
) и Decimal
(decimal
), для которых не существует operator^
.
Даже если вы можете надежно проверить наличие у операторов, нет гарантии, что звонок в operator^
будет делать то, что вы хотите, и сделать обмен возможной. Это просто произвольный метод, и он может что-то делать.
И все это даже не касается того факта, что вы не можете на самом деле не вызов оператор, если вы убедитесь, что ваши общие ограничения описывают исключительно типы с operator^
. Если вы не можете этого сделать, следующим вариантом будет написать специализированную версию для каждого типа T
, которую вы можете назвать operator^
, даже если все эти версии выглядят одинаково. Чтобы вызвать эти специализированные методы, вам понадобятся индивидуальные проверки типов, по одному для каждого типа, упомянутого выше. И ужасные броски между ними, которые, как я полагаю, также вызовут бокс.
ли накладные расходы проверки типа отменить любые преимущества в производительности подкачого XOR?
Да.
Но первое, позвольте мне отметить, что-то:
Type type = T.GetType();
if (type.IsPrimitive || type == typeof(Decimal)) {
я не уверен, должен ли он быть typeof(T)
или a.GetType()
и ваш код не проясняет, что (так как ваш код не является действительным и это немного похоже на любую возможность).В случае, если на самом деле это вызов GetType()
, а не оператор typeof()
, вы должны принять во внимание, что для значения T
это сам тип структуры, оператор typeof(T)
и метод GetType()
дали бы тот же результат, поэтому вы могли бы уйти только с этим:
var type = typeof(T);
if(type.IsPrimitive || type == typeof(Decimal)) {
в любом случае, рассмотрим один из этих двух методов:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsPrimitiveA<T>(T obj) => typeof (T).IsPrimitive;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsPrimitiveB<T>(T obj) => obj.GetType().IsPrimitive;
typeof
версия выпуск разборке (встроенный):
55: var a = IsPrimitiveA(1);
001E0453 B9 BC F6 B2 71 mov ecx,71B2F6BCh
001E0458 E8 E1 A3 76 72 call 7294A83E
001E045D 8B C8 mov ecx,eax
001E045F 8B 01 mov eax,dword ptr [ecx]
001E0461 8B 40 6C mov eax,dword ptr [eax+6Ch]
001E0464 FF 10 call dword ptr [eax]
001E0466 0F B6 F8 movzx edi,al
GetType()
версия выпуск разборке (встроенный):
49: var b = IsPrimitiveB(2);
00360464 C7 45 F4 02 00 00 00 mov dword ptr [ebp-0Ch],2
0036046B B9 BC F6 B2 71 mov ecx,71B2F6BCh
00360470 E8 7F 2C E5 FF call 001B30F4
00360475 8B D0 mov edx,eax
00360477 8B 45 F4 mov eax,dword ptr [ebp-0Ch]
0036047A 89 42 04 mov dword ptr [edx+4],eax
0036047D 8B CA mov ecx,edx
0036047F 39 09 cmp dword ptr [ecx],ecx
00360481 E8 3E C9 62 71 call 7198CDC4
00360486 8B C8 mov ecx,eax
00360488 8B 01 mov eax,dword ptr [ecx]
0036048A 8B 40 6C mov eax,dword ptr [eax+6Ch]
0036048D FF 10 call dword ptr [eax]
0036048F 0F B6 F0 movzx esi,al
00360492 8B CF mov ecx,edi
00360494 E8 5B 24 DC 71 call 721228F4
В любом случае, это очень много кода уже - и мы даже не начали обменивать еще. Если наивный swap не делает так много ходов и звонков, оптимизированная версия, вероятно, уже медленнее, начиная с ее первого оператора.
Но давайте посмотрим, что на самом деле даст наивная версия. Ссылка исходный код:
static void Main(string[] args)
{
var a = 5;
var b = 10;
Swap(ref a, ref b);
Console.WriteLine(a);
Console.WriteLine(b);
}
Generic наивным источник подкачки код:
static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
необщего XOR на основе источника подкачки код всегда будет иметь вид:
static void Swap(ref ... a, ref ... b)
{
a ^= b;
b ^= a;
a ^= b;
}
Я покажу разблокировка выпуска, созданная 32-разрядным JIT-компилятором. Я отказываюсь от 64-битных версий, потому что я не вижу каких-либо значимых изменений (за исключением использования разных регистров), но не стесняйтесь проверять эти случаи для себя. Я также буду опускать шаблон ввода и выйти из шаблона даже для не-встроенных методов.
Вот наивные Swap<byte>
(встраивается):
32: Swap<byte>(ref a, ref b);
00210464 0F B6 55 FC movzx edx,byte ptr [ebp-4]
00210468 0F B6 45 F8 movzx eax,byte ptr [ebp-8]
0021046C 88 45 FC mov byte ptr [ebp-4],al
0021046F 88 55 F8 mov byte ptr [ebp-8],dl
Сравните с XOR на основе Swap(ref byte, ref byte)
(не встраиваемого по умолчанию):
46: a ^= b;
000007FE99F204F0 0F B6 02 movzx eax,byte ptr [rdx]
000007FE99F204F3 30 01 xor byte ptr [rcx],al
47: b ^= a;
000007FE99F204F5 0F B6 01 movzx eax,byte ptr [rcx]
000007FE99F204F8 30 02 xor byte ptr [rdx],al
48: a ^= b;
000007FE99F204FA 0F B6 02 movzx eax,byte ptr [rdx]
000007FE99F204FD 30 01 xor byte ptr [rcx],al
Вот наивные Swap<short>
(встраивается):
32: Swap<short>(ref a, ref b);
00490464 0F BF 55 FC movsx edx,word ptr [ebp-4]
00490468 0F BF 45 F8 movsx eax,word ptr [ebp-8]
0049046C 66 89 45 FC mov word ptr [ebp-4],ax
00490470 66 89 55 F8 mov word ptr [ebp-8],dx
Сравните с XOR на основе Swap(ref short, ref short)
(не встраиваемого по умолчанию):
53: a ^= b;
001E0498 0F BF 02 movsx eax,word ptr [edx]
001E049B 66 31 01 xor word ptr [ecx],ax
54: b ^= a;
001E049E 0F BF 01 movsx eax,word ptr [ecx]
001E04A1 66 31 02 xor word ptr [edx],ax
55: a ^= b;
001E04A4 0F BF 02 movsx eax,word ptr [edx]
001E04A7 66 31 01 xor word ptr [ecx],ax
Вот наивные Swap<int>
(встраивается):
32: Swap<int>(ref a, ref b);
002E0464 8B 55 FC mov edx,dword ptr [ebp-4]
002E0467 8B 45 F8 mov eax,dword ptr [ebp-8]
002E046A 89 45 FC mov dword ptr [ebp-4],eax
002E046D 89 55 F8 mov dword ptr [ebp-8],edx
Сравните с XOR на основе Swap(ref int, ref int)
(не по умолчанию):
60: a ^= b;
003904A0 8B 02 mov eax,dword ptr [edx]
003904A2 31 01 xor dword ptr [ecx],eax
61: b ^= a;
003904A4 8B 01 mov eax,dword ptr [ecx]
003904A6 31 02 xor dword ptr [edx],eax
62: a ^= b;
003904A8 8B 02 mov eax,dword ptr [edx]
003904AA 31 01 xor dword ptr [ecx],eax
Здесь t он наивны Swap<long>
(встраивается):
33: Swap<long>(ref a, ref b);
001D047A 8B 75 F0 mov esi,dword ptr [ebp-10h]
001D047D 8B 7D F4 mov edi,dword ptr [ebp-0Ch]
001D0480 8B 45 E8 mov eax,dword ptr [ebp-18h]
001D0483 8B 55 EC mov edx,dword ptr [ebp-14h]
001D0486 89 45 F0 mov dword ptr [ebp-10h],eax
001D0489 89 55 F4 mov dword ptr [ebp-0Ch],edx
001D048C 89 75 E8 mov dword ptr [ebp-18h],esi
001D048F 89 7D EC mov dword ptr [ebp-14h],edi
Сравните с XOR на основе Swap(ref long, ref long)
(не встраиваемого по умолчанию):
68: a ^= b;
003104B6 8B 06 mov eax,dword ptr [esi]
003104B8 8B 56 04 mov edx,dword ptr [esi+4]
003104BB 33 07 xor eax,dword ptr [edi]
003104BD 33 57 04 xor edx,dword ptr [edi+4]
003104C0 89 06 mov dword ptr [esi],eax
003104C2 89 56 04 mov dword ptr [esi+4],edx
69: b ^= a;
003104C5 8B 07 mov eax,dword ptr [edi]
003104C7 8B 57 04 mov edx,dword ptr [edi+4]
003104CA 33 06 xor eax,dword ptr [esi]
003104CC 33 56 04 xor edx,dword ptr [esi+4]
003104CF 89 07 mov dword ptr [edi],eax
003104D1 89 57 04 mov dword ptr [edi+4],edx
70: a ^= b;
003104D4 8B 06 mov eax,dword ptr [esi]
003104D6 8B 56 04 mov edx,dword ptr [esi+4]
003104D9 33 07 xor eax,dword ptr [edi]
003104DB 33 57 04 xor edx,dword ptr [edi+4]
003104DE 89 06 mov dword ptr [esi],eax
003104E0 89 56 04 mov dword ptr [esi+4],edx
Очевидная идея состоит в том, что подход XOR на основе, кажется, превышает предел, по которому компилятор JIT встрояет методы по умолчанию. К счастью, это будет на самом деле, вставит метод XOR на основе, если вы украшаете это правильно:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Теперь к самой сборки:
Для типов, которые не больше, чем dword
(32- бит без знака) наивный подход всегда испускает инструкции 4 mov
(-семейства), в то время как подход на основе XOR испускает в общей сложности 3 mov
и 3 xor
инструкций. Во всех случаях аргументы находятся в регистре. Я не измерял никаких времен, но если бы мне пришлось догадаться, я бы предположил, что один mov
между регистрами будет не медленнее, чем 3 xor
операций.
Когда вы идете больше dword
, операции разбиваются на отдельные операции dword
. В случае long
(64-разрядное целое число со знаком) наивный подход испускает операции 8 mov
, тогда как подход на основе XOR испускает операции 12 mov
и операции 6 xor
. Это, по-видимому, указывает на то, что особенно для более крупных структур наивный подход более компактен.
Ради экспериментирования, давайте создадим структуру, которая это столь же большой, как decimal
и фактически провозгласить operator^
:
struct Big
{
public long U;
public long V;
public static Big operator ^(Big op1, Big op2)
{
Big b;
b.U = op1.U^op2.U;
b.V = op1.V^op2.V;
return b;
}
}
Это operator^
гранично достаточно мал, чтобы быть встраиваемыми по умолчанию , поэтому я не ожидаю увидеть какие-либо вызовы при разборке.
XOR-версия Swap
для этого типа будет выглядеть так же, как и другие, поэтому я не повторяю ее.
Вот наивные Swap<Big>
(встраиваются):
52: Swap<Big>(ref a, ref b);
001D0484 8B 4D EC mov ecx,dword ptr [ebp-14h]
001D0487 8B 75 F0 mov esi,dword ptr [ebp-10h]
001D048A 8B 45 E8 mov eax,dword ptr [ebp-18h]
001D048D 89 45 D0 mov dword ptr [ebp-30h],eax
001D0490 8B FB mov edi,ebx
001D0492 8B 45 DC mov eax,dword ptr [ebp-24h]
001D0495 8B 55 E0 mov edx,dword ptr [ebp-20h]
001D0498 89 45 EC mov dword ptr [ebp-14h],eax
001D049B 89 55 F0 mov dword ptr [ebp-10h],edx
001D049E 8B 45 D4 mov eax,dword ptr [ebp-2Ch]
001D04A1 8B 55 D8 mov edx,dword ptr [ebp-28h]
001D04A4 89 55 E8 mov dword ptr [ebp-18h],edx
001D04A7 8B D8 mov ebx,eax
001D04A9 89 4D DC mov dword ptr [ebp-24h],ecx
001D04AC 89 75 E0 mov dword ptr [ebp-20h],esi
001D04AF 8B 45 D0 mov eax,dword ptr [ebp-30h]
001D04B2 89 7D D4 mov dword ptr [ebp-2Ch],edi
001D04B5 89 45 D8 mov dword ptr [ebp-28h],eax
Сравнение с XOR на основе Swap(ref Big, ref Big)
(не встраиваемыми):
94: a ^= b;
0027051C 8B 01 mov eax,dword ptr [ecx]
0027051E 8B 51 04 mov edx,dword ptr [ecx+4]
00270521 89 45 E4 mov dword ptr [ebp-1Ch],eax
94: a ^= b;
00270524 89 55 E8 mov dword ptr [ebp-18h],edx
00270527 8B 41 08 mov eax,dword ptr [ecx+8]
0027052A 8B 51 0C mov edx,dword ptr [ecx+0Ch]
0027052D 89 45 DC mov dword ptr [ebp-24h],eax
00270530 89 55 E0 mov dword ptr [ebp-20h],edx
00270533 8B 06 mov eax,dword ptr [esi]
00270535 8B 56 04 mov edx,dword ptr [esi+4]
00270538 89 45 D4 mov dword ptr [ebp-2Ch],eax
0027053B 89 55 D8 mov dword ptr [ebp-28h],edx
0027053E 8B 46 08 mov eax,dword ptr [esi+8]
00270541 8B 56 0C mov edx,dword ptr [esi+0Ch]
00270544 89 45 CC mov dword ptr [ebp-34h],eax
00270547 89 55 D0 mov dword ptr [ebp-30h],edx
0027054A 8B 45 E4 mov eax,dword ptr [ebp-1Ch]
0027054D 8B 55 E8 mov edx,dword ptr [ebp-18h]
00270550 33 45 D4 xor eax,dword ptr [ebp-2Ch]
00270553 33 55 D8 xor edx,dword ptr [ebp-28h]
00270556 89 45 F4 mov dword ptr [ebp-0Ch],eax
00270559 89 55 F8 mov dword ptr [ebp-8],edx
0027055C 8B 45 DC mov eax,dword ptr [ebp-24h]
0027055F 8B 55 E0 mov edx,dword ptr [ebp-20h]
00270562 33 45 CC xor eax,dword ptr [ebp-34h]
00270565 33 55 D0 xor edx,dword ptr [ebp-30h]
00270568 89 45 EC mov dword ptr [ebp-14h],eax
0027056B 89 55 F0 mov dword ptr [ebp-10h],edx
0027056E 8B 45 F4 mov eax,dword ptr [ebp-0Ch]
00270571 8B 55 F8 mov edx,dword ptr [ebp-8]
00270574 89 01 mov dword ptr [ecx],eax
00270576 89 51 04 mov dword ptr [ecx+4],edx
00270579 8B 45 EC mov eax,dword ptr [ebp-14h]
0027057C 8B 55 F0 mov edx,dword ptr [ebp-10h]
0027057F 89 41 08 mov dword ptr [ecx+8],eax
00270582 89 51 0C mov dword ptr [ecx+0Ch],edx
95: b ^= a;
00270585 8B 06 mov eax,dword ptr [esi]
00270587 8B 56 04 mov edx,dword ptr [esi+4]
95: b ^= a;
0027058A 89 45 B4 mov dword ptr [ebp-4Ch],eax
0027058D 89 55 B8 mov dword ptr [ebp-48h],edx
00270590 8B 46 08 mov eax,dword ptr [esi+8]
00270593 8B 56 0C mov edx,dword ptr [esi+0Ch]
00270596 89 45 AC mov dword ptr [ebp-54h],eax
00270599 89 55 B0 mov dword ptr [ebp-50h],edx
0027059C 8B 01 mov eax,dword ptr [ecx]
0027059E 8B 51 04 mov edx,dword ptr [ecx+4]
002705A1 89 45 A4 mov dword ptr [ebp-5Ch],eax
002705A4 89 55 A8 mov dword ptr [ebp-58h],edx
002705A7 8B 41 08 mov eax,dword ptr [ecx+8]
002705AA 8B 51 0C mov edx,dword ptr [ecx+0Ch]
002705AD 89 45 9C mov dword ptr [ebp-64h],eax
002705B0 89 55 A0 mov dword ptr [ebp-60h],edx
002705B3 8B 45 B4 mov eax,dword ptr [ebp-4Ch]
002705B6 8B 55 B8 mov edx,dword ptr [ebp-48h]
002705B9 33 45 A4 xor eax,dword ptr [ebp-5Ch]
002705BC 33 55 A8 xor edx,dword ptr [ebp-58h]
002705BF 89 45 C4 mov dword ptr [ebp-3Ch],eax
002705C2 89 55 C8 mov dword ptr [ebp-38h],edx
002705C5 8B 45 AC mov eax,dword ptr [ebp-54h]
002705C8 8B 55 B0 mov edx,dword ptr [ebp-50h]
002705CB 33 45 9C xor eax,dword ptr [ebp-64h]
002705CE 33 55 A0 xor edx,dword ptr [ebp-60h]
002705D1 89 45 BC mov dword ptr [ebp-44h],eax
002705D4 89 55 C0 mov dword ptr [ebp-40h],edx
002705D7 8B 45 C4 mov eax,dword ptr [ebp-3Ch]
002705DA 8B 55 C8 mov edx,dword ptr [ebp-38h]
002705DD 89 06 mov dword ptr [esi],eax
002705DF 89 56 04 mov dword ptr [esi+4],edx
002705E2 8B 45 BC mov eax,dword ptr [ebp-44h]
002705E5 8B 55 C0 mov edx,dword ptr [ebp-40h]
002705E8 89 46 08 mov dword ptr [esi+8],eax
002705EB 89 56 0C mov dword ptr [esi+0Ch],edx
96: a ^= b;
002705EE 8B 01 mov eax,dword ptr [ecx]
002705F0 8B 51 04 mov edx,dword ptr [ecx+4]
002705F3 89 45 84 mov dword ptr [ebp-7Ch],eax
002705F6 89 55 88 mov dword ptr [ebp-78h],edx
002705F9 8B 41 08 mov eax,dword ptr [ecx+8]
002705FC 8B 51 0C mov edx,dword ptr [ecx+0Ch]
002705FF 89 85 7C FF FF FF mov dword ptr [ebp-84h],eax
00270605 89 55 80 mov dword ptr [ebp-80h],edx
00270608 8B 06 mov eax,dword ptr [esi]
0027060A 8B 56 04 mov edx,dword ptr [esi+4]
0027060D 89 85 74 FF FF FF mov dword ptr [ebp-8Ch],eax
00270613 89 95 78 FF FF FF mov dword ptr [ebp-88h],edx
00270619 8B 46 08 mov eax,dword ptr [esi+8]
0027061C 8B 56 0C mov edx,dword ptr [esi+0Ch]
0027061F 89 85 6C FF FF FF mov dword ptr [ebp-94h],eax
00270625 89 95 70 FF FF FF mov dword ptr [ebp-90h],edx
0027062B 8B 45 84 mov eax,dword ptr [ebp-7Ch]
0027062E 8B 55 88 mov edx,dword ptr [ebp-78h]
00270631 33 85 74 FF FF FF xor eax,dword ptr [ebp-8Ch]
00270637 33 95 78 FF FF FF xor edx,dword ptr [ebp-88h]
0027063D 89 45 94 mov dword ptr [ebp-6Ch],eax
00270640 89 55 98 mov dword ptr [ebp-68h],edx
00270643 8B 85 7C FF FF FF mov eax,dword ptr [ebp-84h]
00270649 8B 55 80 mov edx,dword ptr [ebp-80h]
0027064C 33 85 6C FF FF FF xor eax,dword ptr [ebp-94h]
00270652 33 95 70 FF FF FF xor edx,dword ptr [ebp-90h]
00270658 89 45 8C mov dword ptr [ebp-74h],eax
0027065B 89 55 90 mov dword ptr [ebp-70h],edx
0027065E 8B 45 94 mov eax,dword ptr [ebp-6Ch]
00270661 8B 55 98 mov edx,dword ptr [ebp-68h]
00270664 89 01 mov dword ptr [ecx],eax
00270666 89 51 04 mov dword ptr [ecx+4],edx
00270669 8B 45 8C mov eax,dword ptr [ebp-74h]
0027066C 8B 55 90 mov edx,dword ptr [ebp-70h]
0027066F 89 41 08 mov dword ptr [ecx+8],eax
00270672 89 51 0C mov dword ptr [ecx+0Ch],edx
Как Структуры получить больше, он становится яснее, что вы «Лучше с наивным подходом.
Однако здесь есть что-то интересное. Рассмотрим попытку предотвратить встраивание из Swap<T>
:
[MethodImpl(MethodImplOptions.NoInlining)]
static void Swap<T>(ref T a, ref T b)
{ /* ... */ }
Вот наивные Swap<Big>
(встраивание подавлено):
93: Big temp = a;
004C0502 EC in al,dx
004C0503 57 push edi
004C0504 56 push esi
004C0505 53 push ebx
004C0506 83 EC 10 sub esp,10h
004C0509 8B DA mov ebx,edx
004C050B 8B 01 mov eax,dword ptr [ecx]
004C050D 8B 51 04 mov edx,dword ptr [ecx+4]
004C0510 89 45 EC mov dword ptr [ebp-14h],eax
004C0513 89 55 F0 mov dword ptr [ebp-10h],edx
004C0516 8B 41 08 mov eax,dword ptr [ecx+8]
004C0519 8B 51 0C mov edx,dword ptr [ecx+0Ch]
004C051C 89 45 E4 mov dword ptr [ebp-1Ch],eax
004C051F 89 55 E8 mov dword ptr [ebp-18h],edx
94: a = b;
004C0522 8B F9 mov edi,ecx
004C0524 8B F3 mov esi,ebx
004C0526 F3 0F 7E 06 movq xmm0,mmword ptr [esi]
004C052A 66 0F D6 07 movq mmword ptr [edi],xmm0
004C052E F3 0F 7E 46 08 movq xmm0,mmword ptr [esi+8]
004C0533 66 0F D6 47 08 movq mmword ptr [edi+8],xmm0
95: b = temp;
004C0538 8B 45 EC mov eax,dword ptr [ebp-14h]
004C053B 8B 55 F0 mov edx,dword ptr [ebp-10h]
004C053E 89 03 mov dword ptr [ebx],eax
004C0540 89 53 04 mov dword ptr [ebx+4],edx
004C0543 8B 45 E4 mov eax,dword ptr [ebp-1Ch]
004C0546 8B 55 E8 mov edx,dword ptr [ebp-18h]
004C0549 89 43 08 mov dword ptr [ebx+8],eax
004C054C 89 53 0C mov dword ptr [ebx+0Ch],edx
Теперь он начинает испускать инструкции SSE! Такое же поведение наблюдается для decimal
и других больших структур.
Основанный на XOR подход не генерирует инструкции SSE ни при каких обстоятельствах, которые я тестировал.
Давайте попробуем с классом:
public class W<T>
{
public T Value;
}
Вот наивные Swap<W<T>>
(встраиваются):
61: Swap(ref a, ref b);
0023047E 8B 55 FC mov edx,dword ptr [ebp-4]
00230481 8B 45 F8 mov eax,dword ptr [ebp-8]
00230484 89 45 FC mov dword ptr [ebp-4],eax
00230487 89 55 F8 mov dword ptr [ebp-8],edx
Это довольно просто - это фактически является int
своп, как указано выше.
Ссылки непрозрачны, и поэтому подход XOR не имеет смысла для этих типов. Таким образом, в этом случае нет эквивалентной разборки.
Для типов, таких как float
, double
и decimal
нет элегантного способа применить операцию XOR.Один (возможно глупый) подход, чтобы сделать это вообще возможно, чтобы создать союз, который должен быть не родовое:
[StructLayout(LayoutKind.Explicit)]
struct XORFloat
{
[FieldOffset(0)] public int Bits;
[FieldOffset(0)] public float Value;
}
Тогда попытка это:
static void Swap(ref float a, ref float b)
{
var _a = default(XORFloat);
var _b = default(XORFloat);
_a.Value = a;
_b.Value = b;
_a.Bits ^= _b.Bits;
_b.Bits ^= _a.Bits;
_a.Bits ^= _b.Bits;
a = _a.Value;
b = _b.Value;
}
Но это, кажется, чтобы победить целая точка подхода, основанного на XOR, потому что он будет явно включать в себя множество операций mov
.
Вот наивные Swap<float>
(встраивается):
19: Swap<float>(ref a, ref b);
00252DC4 D9 45 FC fld dword ptr [ebp-4]
00252DC7 D9 45 F8 fld dword ptr [ebp-8]
00252DCA D9 5D FC fstp dword ptr [ebp-4]
00252DCD D9 5D F8 fstp dword ptr [ebp-8]
Сравните с XOR на основе (и накидной основе) Swap(ref float, ref float)
(не встраиваемый по умолчанию):
153: var _a = default(XORFloat);
0035049F 33 C0 xor eax,eax
003504A1 89 45 F8 mov dword ptr [ebp-8],eax
003504A4 89 45 F4 mov dword ptr [ebp-0Ch],eax
003504A7 8D 45 F8 lea eax,[ebp-8]
003504AA 33 F6 xor esi,esi
003504AC 89 30 mov dword ptr [eax],esi
154: var _b = default(XORFloat);
003504AE 8D 45 F4 lea eax,[ebp-0Ch]
003504B1 89 30 mov dword ptr [eax],esi
155: _a.Value = a;
003504B3 D9 01 fld dword ptr [ecx]
003504B5 D9 5D F8 fstp dword ptr [ebp-8]
156: _b.Value = b;
003504B8 D9 02 fld dword ptr [edx]
003504BA D9 5D F4 fstp dword ptr [ebp-0Ch]
157: _a.Bits ^= _b.Bits;
003504BD 8D 75 F8 lea esi,[ebp-8]
003504C0 8B 45 F4 mov eax,dword ptr [ebp-0Ch]
003504C3 31 06 xor dword ptr [esi],eax
158: _b.Bits ^= _a.Bits;
003504C5 8D 75 F4 lea esi,[ebp-0Ch]
003504C8 8B 45 F8 mov eax,dword ptr [ebp-8]
003504CB 31 06 xor dword ptr [esi],eax
159: _a.Bits ^= _b.Bits;
003504CD 8D 75 F8 lea esi,[ebp-8]
003504D0 8B 45 F4 mov eax,dword ptr [ebp-0Ch]
003504D3 31 06 xor dword ptr [esi],eax
160: a = _a.Value;
003504D5 D9 45 F8 fld dword ptr [ebp-8]
003504D8 D9 19 fstp dword ptr [ecx]
161: b = _b.Value;
003504DA D9 45 F4 fld dword ptr [ebp-0Ch]
003504DD D9 1A fstp dword ptr [edx]
В заключение я бы сказал, используя очевидное наивное Swap
и дайте компилятору понять, что вы имеете в виду и выполняете свою работу. Есть еще интересные вещи, чтобы возиться.
Проверка типа, безусловно, отменяет любые преимущества использования XOR вместо обычной замены. – MarcinJuraszek
Почему бы не использовать очень эффективный 'Interlocked.Exchange (T, T)' для ваших ссылочных типов и одну из перегрузок для явного типа значения, а не для создания обертки? https://msdn.microsoft.com/en-us/library/system.threading.interlocked.exchange(v=vs.110).aspx. Ваша обертка имеет измеримые накладные расходы при циклической сложности. –
MickyD
@MickyD. Блокировка является атомарной. Атомные операции, как правило, более дорогостоящие. –