Как поменять местами указатели?

Как я могу эффективно поменять местами указатели в Delphi? Я пытаюсь обменять указатели на целочисленный тип. Следующий пример работает, однако I2 равен 0 при компиляции с 64-битной версией.

program Project11;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

procedure Swap(const P1, P2);
asm
{$if defined(CPUX86)}
  xchg ecx, [eax]
  xchg ecx, [edx]
  xchg [eax], ecx
{$else}
  xchg rcx, [rax]
  xchg rcx, [rdx]
  xchg [rax], rcx
{$endif}
end;

var
  I1, I2: Integer;

begin
  I1 := 19;
  I2 := 564;

  WriteLn('Swapping..');

  WriteLn('I1: '+I1.ToString());
  WriteLn('I2: '+I2.ToString());

  Swap(I1, I2);

  WriteLn('I1: '+I1.ToString());
  WriteLn('I2: '+I2.ToString());

  ReadLn;
end.

person user3764855    schedule 25.06.2014    source источник
comment
Реализация процедуры подкачки на ассемблере не даст заметного прироста производительности по сравнению с простым использованием временной переменной локали. И если вам нужна безопасность потоков, вы можете использовать TInterlocked.Exchange.   -  person Stefan Glienke    schedule 26.06.2014
comment
@StefanGlienke На самом деле я бы использовал LOCK XCHG   -  person user3764855    schedule 26.06.2014
comment
Именно это и делает TInterlocked.Exchange... но он также работает и на других платформах.   -  person Stefan Glienke    schedule 26.06.2014


Ответы (2)


Я делаю это так:

type
  TGeneric = class
  public
    class procedure Swap<T>(var Left, Right: T); static;
  end;

class procedure TGeneric.Swap<T>(var Left, Right: T);
var
  temp: T;
begin
  temp := Left;
  Left := Right;
  Right := temp;
end;

Это можно использовать для замены значений любого типа. Возможность, которая заставит плакать любого Java-программиста! ;-)

На мой взгляд, компилятор производит довольно эффективный код. Я не думаю, что вам нужно беспокоиться о способности компилятора создавать эффективный код для выполнения назначений.

Для 32 бит с T = Pointer имеем:

push ebx
mov ecx,[eax]
mov ebx,[edx]
mov [eax],ebx
mov [edx],ecx
pop ebx
ret 

Для 64 бит с T = Pointer имеем:

mov rax,[rcx]
mov r8,[rdx]
mov [rcx],r8
mov [rdx],rax
ret

FWIW, мне не нравятся ваши версии с использованием const параметров. Код в вопросе должен использовать var. Но безопасность типов этой универсальной версии, конечно, предпочтительнее.

person David Heffernan    schedule 25.06.2014
comment
Это сработает, но это небезопасно для типов. Так что я бы этого избегал. Лучше позволить компилятору найти ваши ошибки там, где он может. - person David Heffernan; 25.06.2014
comment
Эм. Я не называю это эффективным кодом. Версия ASM будет обходить это. MOV медленнее, чем XCHG. - person user3764855; 07.07.2014
comment
@user3764855 user3764855 Каково ваше время для этого? - person David Heffernan; 07.07.2014
comment
4x MOV + 2x POP против 3x XCHG делаем математику. :) - person user3764855; 07.07.2014
comment
У вас есть тайминги? Подсчет операций не измеряет фактическое время выполнения. Я бы предположил, что горлышко бутылки - это чтение памяти. - person David Heffernan; 07.07.2014
comment
Кроме того, MOV делает копию, XCHG просто меняет указатели. - person user3764855; 07.07.2014
comment
@user xchg ecx, [eax] Обратите внимание на [eax]. Это память для чтения и записи. Если вы заботитесь о производительности, определите время кода. Не считайте коды операций. - person David Heffernan; 07.07.2014
comment
В вашей версии xchg asm я считаю 3 чтения основной памяти и 3 записи в основную память. В версии на основе mov, созданной компилятором, я считаю 2 чтения и 2 записи. Я предлагаю вам провести сравнительный анализ, прежде чем делать какие-либо выводы. - person David Heffernan; 07.07.2014
comment
Вы также можете использовать 1x XCHG + 2 MOV - person user3764855; 07.07.2014
comment
Вы уже проводили этот временной эксперимент? - person David Heffernan; 07.07.2014
comment
Я проведу быструю сортировку по умолчанию и asm. Я опубликую это здесь. - person user3764855; 07.07.2014
comment
Мой очень быстрый первый тест показывает, что код в моем ответе примерно в 5 раз быстрее, чем asm в вопросе. Я меняю местами значения в массивах длиной 5000. Я делаю это 100000 раз, чтобы получить разумное время выполнения. Если я могу вас чему-то научить, так это тому, что оптимизация без бенчмаркинга — это не оптимизация. Если вы оптимизируете таким образом, не удивляйтесь, если вы обнаружите, что ваш оптимизированный код медленнее исходного! - person David Heffernan; 07.07.2014
comment
Ах да, pastebin.com/xk3fYV4e работает быстрее, чем SwapPas. Стабильно примерно на 300-500 мс. По крайней мере на 32 бит. - person user3764855; 07.07.2014
comment
Я меняю местами 32-битные целые числа в своем тесте. Я не вижу актуальности вашего pastebin. Он будет скомпилирован в тот же код, что и мой SwapPas. Суть в том, что я надеюсь, что понял, что время и бенчмаркинг имеют решающее значение. - person David Heffernan; 07.07.2014
comment
Вы поторопились со своим предположением. Это на 20-50% быстрее на моем Q6600 pastebin.com/75A756j4 - person user3764855; 08.07.2014
comment
Я вообще никаких предположений не делал. Все, что я сказал, было то, что это было важно для профиля. Кажется, вы получили сообщение, что означает, что я сделал свою работу! Тем не менее, кажется странным, что память работает быстрее, чем регистры. Я надеюсь, вы строите сборку релиза? - person David Heffernan; 08.07.2014
comment
да. Он быстрее в обеих сборках. Очевидно больше в отладке. - person user3764855; 08.07.2014
comment
Меня это удивляет, но это прекрасная иллюстрация того, что я хочу сказать. - person David Heffernan; 08.07.2014
comment
Зачем использовать класс? - person Marus Nebunu; 29.08.2018
comment
@Marus, чтобы мы могли использовать общий метод - person David Heffernan; 29.08.2018

Ответ Дэвида — это самый разумный подход к решению проблемы.

Однако, чтобы выяснить, почему ваш код не работает, обратите внимание на две вещи.

  • Integer — это 32-битный тип как для x86, так и для x64.
  • В x64 аргументы передаются (по порядку) в RCX, RDX, R8 и R9

При компиляции для x86 ваш код работает. При компиляции для X64 вы предполагаете, что аргументы передаются в RAX и RDX (например, EAX, EDX в x86), что неверно. Итак, ваш код должен выглядеть так

  xchg rax, [rcx]
  xchg rax, [rdx]
  xchg [rcx], rax

Вы заметите, что без изменений это также не работает - теперь и I1, и I2 равны нулю. Это связано с тем, что вы предположили, что Integer является 64-битным типом, что, в отличие от указателей, не является таковым (и вы вообще не выполняете проверку типов - см. еще раз преимущества решения Дэвида). Если переопределить:

var
  {$if defined(CPUX86)}
    I1, I2: Integer;
  {$else}
    I1, I2: Int64;    
  {$ifend}

Потом вдруг все работает как положено. К настоящему времени также должно быть ясно, почему это не самый элегантный подход.

person J...    schedule 25.06.2014
comment
Я предполагаю, что что-то вроде xchg eax, [rcx] xchg eax, [rdx] сделает 32-битный обмен xchg [rcx], eax - person David Heffernan; 26.06.2014
comment
Лучше использовать NativeInt. - person user3764855; 26.06.2014
comment
@ user3764855 Было бы лучше использовать что-то безопасное. - person J...; 26.06.2014
comment
@DavidHeffernan Да, но я предполагаю, что OP хочет поменять местами указатели (как указывает вопрос). Запутанной частью является выбор типа значения (Integer) для демонстрации метода, а не ссылочного типа или указателя (оба из которых, по крайней мере, гарантированно имеют размер NativeInt для данной целевой платформы). - person J...; 26.06.2014
comment
Я предполагаю, что вместо того, чтобы предлагать Int64, мы просто переключимся на указатели и сможем покончить с условным кодом. Да, выбор целого числа странный. - person David Heffernan; 26.06.2014