powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Ну очень быстрый Move() для x86/x64
25 сообщений из 38, страница 1 из 2
Ну очень быстрый Move() для x86/x64
    #38778988
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Привет
Все знают, какой я фанат оптимизаций, и вместе с тем нелюбитель писать тесты замера скорости

Так вот несколько месяцев назад я с ужасом обнаружил, что используемые ранее (REP) MOVS команды - тормозные. Что обычный цикл записи работает быстрее. Кроме того реализация Move() в Delphi x64 оставляет желать лучшего мягко говоря (впрочем как и многое в RTL x64).

Если я ничего не путаю, то скомпилированные x86 приложения в новых версиях Delphi не работают на машинах без поддержки SSE2. Это даёт мне все основания реализовать Move() через SSE(1).

Весомое значение в стандартных Move() реализациях занимает ситуация с пересекающимися областями. Мне эта особенность нужна редко (впрочем как и многим). И поскольку я планирую реформу CachedBuffers, достаточно много внимания было уделено и Move(), потому что библиотека целиком и полностью посвящена копировании памяти.

В итоге реализация выполнена, листинг приведу ниже, функция полностью проверена и отлажена для платформ x86 и x64.
Поэтому:
1) Берите кому нужно
2) Если есть желание делать сравнительный тест скорости и вносить изменения - я за. Но гарантирую, что сам писать такой тест не буду. Нужно определиться с условиями, искать конкурентные реализации, закладываться на разное выравнивание. У меня на это времени и желания нет. Со своей стороны могу гарантировать лучшую производительность на x64 и для больших объёмов на x86. Но насколько именно - я увы не знаю. Have fun
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
// SSE-based non-collision Move() realization
procedure NonCollisionMove(const Source; var Dest; const Size: NativeUInt);
asm
  // basic routine
  {$ifdef CPUX86}
    cmp ecx, 32
  {$else .CPUX64}
    cmp r8, 32
    // make Source = eax/rax, Dest = edx/rdx, Size = ecx/rcx
    mov rax, rcx
    xchg rcx, r8
    // r9 as pointer to @move_03_items
    lea r9, [@move_03_items]
  {$endif}

  // is big/large (32...inf)
  jae @move_big

  // is small (0..3)
  cmp ecx, 4
  jb @move_03

  // move middle(4..31) = move 16(0..16) + move dwords(0..12) + move small(0..3)
  cmp ecx, 16
  jb @move_015

  {$ifdef CPUX86}
    movups xmm0, [eax]
    movups [edx], xmm0
    jne @move_015_offset
    ret
  @move_015_offset:
    sub ecx, 16
    add eax, 16
    add edx, 16
  @move_015:
    push ecx
    and ecx, -4
    add eax, ecx
    add edx, ecx
    jmp [ecx + @move_dwords]
    @move_dwords: DD @rw_0,@rw_4,@rw_8,@rw_12
    @rw_12:
      mov ecx, [eax-12]
      mov [edx-12], ecx
    @rw_8:
      mov ecx, [eax-8]
      mov [edx-8], ecx
    @rw_4:
      mov ecx, [eax-4]
      mov [edx-4], ecx
    @rw_0:
    pop ecx
    and ecx, 3
  {$else .CPUX64}
    movups xmm0, [rax]
    movups [rdx], xmm0
    jne @move_015_offset
    ret
  @move_015_offset:
    sub rcx, 16
    add rax, 16
    add rdx, 16
  @move_015:
    // make r9 = dest 0..3 pointer, rcx = dwords count
    mov r8, rcx
    shr rcx, 2
    and r8, 3
    lea r9, [r9 + r8*8]
    // case jump
    lea r8, [@move_dwords]
    jmp qword ptr [r8 + rcx*8]
    @move_dwords: DQ @rw_0,@rw_4,@rw_8,@rw_12
    @rw_8:
      mov rcx, [rax]
      mov [rdx], rcx
      add rax, 8
      add rdx, 8
    jmp qword ptr [r9]
    @rw_12:
      mov rcx, [rax]
      mov [rdx], rcx
      add rax, 8
      add rdx, 8
    @rw_4:
      mov ecx, [rax]
      mov [rdx], ecx
      add rax, 4
      add rdx, 4
    @rw_0:  
    jmp qword ptr [r9]
  {$endif}

@move_03:
  {$ifdef CPUX86}
    jmp [offset @move_03_items + ecx*4]
    @move_03_items: DD @0,@1,@2,@3
    @2: mov cx, [eax]
        mov [edx], cx
        ret
    @3: mov cx, [eax]
        mov [edx], cx
        add eax, 2
        add edx, 2
    @1: mov cl, [eax]
        mov [edx], cl
    @0: ret
  {$else .CPUX64}
    jmp qword ptr [r9 + rcx*8]
    @move_03_items: DQ @0,@1,@2,@3
    @2: mov cx, [rax]
        mov [rdx], cx
        ret
    @3: mov cx, [rax]
        mov [rdx], cx
        add rax, 2
        add rdx, 2
    @1: mov cl, [rax]
        mov [rdx], cl
    @0: ret
  {$endif}

@move_big:
  {$ifdef CPUX86}
    cmp ecx, 16*4
  {$else .CPUX64}
    cmp rcx, 16*4
  {$endif}
  jae @move_large
  
  // big memory move by SSE (32..63) = (32..48) + (0..15)
  {$ifdef CPUX86}
     test ecx, 15
     jz @move_32_48

     push ecx
     and ecx, 15
     movups xmm0, [eax]
     movups [edx], xmm0
     add eax, ecx
     add edx, ecx

     pop ecx
     and ecx, -16
  {$else .CPUX64}
     mov r8, rcx
     test rcx, 15
     jz @move_32_48

     and r8, 15
     movups xmm0, [rax]
     movups [rdx], xmm0
     add rax, r8
     add rdx, r8

     and rcx, -16
  {$endif}

@move_32_48:
  {$ifdef CPUX86}
    add eax, ecx
    add edx, ecx
    cmp ecx, 48
    jb @rw_32
    @rw_48: movups xmm2, [eax - 2*16 - 16]
            movups [edx - 2*16 - 16], xmm2
    @rw_32: movups xmm1, [eax - 1*16 - 16]
            movups xmm0, [eax - 0*16 - 16]
            movups [edx - 1*16 - 16], xmm1
            movups [edx - 0*16 - 16], xmm0
  {$else .CPUX64}
    add rax, rcx
    add rdx, rcx
    cmp rcx, 48
    jb @rw_32    
    @rw_48: movups xmm2, [rax - 2*16 - 16]
            movups [rdx - 2*16 - 16], xmm2
    @rw_32: movups xmm1, [rax - 1*16 - 16]
            movups xmm0, [rax - 0*16 - 16]
            movups [rdx - 1*16 - 16], xmm1
            movups [rdx - 0*16 - 16], xmm0
  {$endif}

  ret
@move_large:
  // large memory move by SSE (64..inf)

  // destination alignment
  {$ifdef CPUX86}
    push ebx
    test edx, 15
    jz @move_16128_initialize

    mov ebx, edx
    movups xmm0, [eax]
    movups [ebx], xmm0

    add edx, 15
    and edx, -16
    sub ebx, edx
    sub eax, ebx
    add ecx, ebx
  {$else .CPUX64}
    test rdx, 15
    jz @move_16128_initialize

    mov r8, rdx
    movups xmm0, [rax]
    movups [r8], xmm0

    add rdx, 15
    and rdx, -16
    sub r8, rdx
    sub rax, r8
    add rcx, r8
  {$endif}

@move_16128_initialize:
  {$ifdef CPUX86}
    push ecx
    mov ebx, offset @aligned_reads
    shr ecx, 4
    test eax, 15
    jz @move_16128
    mov ebx, offset @unaligned_reads
  {$else .CPUX64}
    mov r8, rcx
    lea r9, [@aligned_reads]
    shr rcx, 4
    test rax, 15
    jz @move_16128
    lea r9, [@unaligned_reads]
  {$endif}

@move_16128:
  {$ifdef CPUX86}
    cmp ecx, 8
    jae @move_128

    lea ecx, [ecx + ecx]
    lea eax, [eax + ecx*8]
    lea edx, [edx + ecx*8]
    lea ebx, [ebx + 8*4]
    neg ecx
    lea ebx, [ebx + ecx*2]
    jmp ebx
  @move_128:
    lea eax, [eax + 128]
    lea edx, [edx + 128]
    lea ecx, [ecx - 8]
    jmp ebx
  {$else .CPUX64}
    cmp rcx, 8
    jae @move_128

    lea rcx, [rcx + rcx]
    lea rax, [rax + rcx*8]
    lea rdx, [rdx + rcx*8]
    lea r9, [r9 + 8*4]
    neg rcx
    lea r9, [r9 + rcx*2]
    jmp r9
  @move_128:
    lea rax, [rax + 128]
    lea rdx, [rdx + 128]
    lea rcx, [rcx - 8]
    jmp r9
  {$endif}

  // aligned sse read
  @aligned_reads:
  {$ifdef CPUX86}
    movaps xmm7, [eax - 7*16 - 16]
    movaps xmm6, [eax - 6*16 - 16]
    movaps xmm5, [eax - 5*16 - 16]
    movaps xmm4, [eax - 4*16 - 16]
    movaps xmm3, [eax - 3*16 - 16]
    movaps xmm2, [eax - 2*16 - 16]
    movaps xmm1, [eax - 1*16 - 16]
    movaps xmm0, [eax - 0*16 - 16]
  {$else .CPUX64}
    movaps xmm7, [rax - 7*16 - 16]
    movaps xmm6, [rax - 6*16 - 16]
    movaps xmm5, [rax - 5*16 - 16]
    movaps xmm4, [rax - 4*16 - 16]
    movaps xmm3, [rax - 3*16 - 16]
    movaps xmm2, [rax - 2*16 - 16]
    movaps xmm1, [rax - 1*16 - 16]
    movaps xmm0, [rax - 0*16 - 16]
  {$endif}
  jae @aligned_writes
  jmp @write_16112

  // unaligned sse read
  @unaligned_reads:
  {$ifdef CPUX86}
    movups xmm7, [eax - 7*16 - 16]
    movups xmm6, [eax - 6*16 - 16]
    movups xmm5, [eax - 5*16 - 16]
    movups xmm4, [eax - 4*16 - 16]
    movups xmm3, [eax - 3*16 - 16]
    movups xmm2, [eax - 2*16 - 16]
    movups xmm1, [eax - 1*16 - 16]
    movups xmm0, [eax - 0*16 - 16]
    jae @aligned_writes
  @write_16112:
    lea ebx, [offset @aligned_writes + 8*4 + ecx*2]
    jmp ebx
  {$else .CPUX64}
    movups xmm7, [rax - 7*16 - 16]
    movups xmm6, [rax - 6*16 - 16]
    movups xmm5, [rax - 5*16 - 16]
    movups xmm4, [rax - 4*16 - 16]
    movups xmm3, [rax - 3*16 - 16]
    movups xmm2, [rax - 2*16 - 16]
    movups xmm1, [rax - 1*16 - 16]
    movups xmm0, [rax - 0*16 - 16]
    jae @aligned_writes
  @write_16112:
    lea r9, [@aligned_writes + 8*4]
    lea r9, [r9 + rcx*2]
    jmp r9
  {$endif}

  // aligned sse write, loop
  @aligned_writes:
  {$ifdef CPUX86}
    movaps [edx - 7*16 - 16], xmm7
    movaps [edx - 6*16 - 16], xmm6
    movaps [edx - 5*16 - 16], xmm5
    movaps [edx - 4*16 - 16], xmm4
    movaps [edx - 3*16 - 16], xmm3
    movaps [edx - 2*16 - 16], xmm2
    movaps [edx - 1*16 - 16], xmm1
    movaps [edx - 0*16 - 16], xmm0
    test ecx, ecx
  {$else .CPUX64}
    movaps [rdx - 7*16 - 16], xmm7
    movaps [rdx - 6*16 - 16], xmm6
    movaps [rdx - 5*16 - 16], xmm5
    movaps [rdx - 4*16 - 16], xmm4
    movaps [rdx - 3*16 - 16], xmm3
    movaps [rdx - 2*16 - 16], xmm2
    movaps [rdx - 1*16 - 16], xmm1
    movaps [rdx - 0*16 - 16], xmm0
    test rcx, rcx
  {$endif}
  jg @move_16128

  // last 0..15 bytes
  {$ifdef CPUX86}
    pop ecx
    pop ebx
    and ecx, 15
    jnz @move_115
    ret
  @move_115:
    add eax, ecx
    add edx, ecx
    movups xmm0, [eax - 0*16 - 16]
    movups [edx - 0*16 - 16], xmm0
  {$else .CPUX64}
    and r8, 15
    jnz @move_115
    ret
  @move_115:
    add rax, r8
    add rdx, r8
    movups xmm0, [rax - 0*16 - 16]
    movups [rdx - 0*16 - 16], xmm0
  {$endif}
end;
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38778994
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
// для поддержки старых версий

{$if (CompilerVersion < 23) and (not Defined(FPC))}
  {$define CPUX86}
{$ifend}

{$if CompilerVersion < 19}
type
  NativeInt = Integer;
  PNativeInt = PInteger;
  NativeUInt = Cardinal;
  PNativeUInt = PCardinal;
{$ifend}
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38778998
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUнесколько месяцев назад я с ужасом обнаружил, что используемые ранее
(REP) MOVS команды - тормозные
Ещё через пару лет ты откроешь для себя технику Zero-Copy, так что забей.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779011
asviridenkov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

Ну ты хоть какие-то тесты проведи, а то так совсем неинтересно.
Ну, типа, на пересылке 100Кб данных работает в 3 раза быстрее чем стандартная move
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779016
vavan
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
можно старые бенчи от FastCode поднять
а потом все равно придет шарахов и всех разгонит
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779017
asviridenkov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

Кстати, раз ты такой фанат оптимизации... У меня, по опыту, на общую производительность влияет больше не move а функция поиска символа в строке. Ниже приведу текущую реализацию. Слабо улучшить?

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
{$IFDEF CPUx64}
function Q_PStrScan(P: pointer{RCX}; Ch: WideChar{RDX}; ASize: Integer{R8}): Integer; //x64, Unicode
asm
.NOFRAME
        TEST    RCX,RCX
        JE      @@m2
        TEST    R8,R8
        JE      @@m2
        MOV     R9,RDI
        MOV     RDI,RCX   //EDI=P
        MOV     RAX,RDX
        MOV     RDX,RCX   //EDX=P
        MOV     RCX,R8
        REPNE   SCASW
        JNE     @@m1
        MOV     RAX,RDI
        SUB     RAX,RDX
        SHR     RAX,1
        MOV     RDI,R9
        RET
@@m1:   MOV     RDI,R9
@@m2:   XOR     RAX,RAX
end;
{$ELSE}
{$IFDEF UNICODE}
function Q_PStrScan(P: pointer; Ch: Char; ASize: Integer): Integer;  //x32, Unicode
asm
        TEST    EAX,EAX   //P=nil?
        JE      @@qt
        TEST    ECX,ECX   //ASize=0?
        JE      @@qt
        PUSH    EDI
        MOV     EDI,EAX   //EDI=P
        XCHG    EAX,EDX   //EDX=P
        REPNE   SCASW
        JNE     @@m1
        MOV     EAX,EDI
        SUB     EAX,EDX
        SHR     EAX,1
        POP     EDI
        RET
@@m1:   POP     EDI
        XOR     EAX,EAX
@@qt:
end;
{$ELSE}
function Q_PStrScan(P: pointer; Ch: Char; ASize: Integer): Integer; //x32, Ansi
asm
        TEST    EAX,EAX
        JE      @@qt
        TEST    ECX,ECX
        JE      @@qt
        PUSH    EDI
        MOV     EDI,EAX   //EDI=P
        XCHG    EAX,EDX   //EDX=P
        REPNE   SCASB
        JNE     @@m1
        MOV     EAX,EDI
        SUB     EAX,EDX
        POP     EDI
        RET
@@m1:   POP     EDI
        XOR     EAX,EAX
@@qt:
end;
{$ENDIF} //Unicode
{$ENDIF} //x64
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779038
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry SibiryakovЕщё через пару лет ты откроешь для себя технику Zero-Copy, так что забей.
Как ты предлагаешь Zero-Copy вставить в Move? :)

asviridenkovНу ты хоть какие-то тесты проведи, а то так совсем неинтересно.
Ну, типа, на пересылке 100Кб данных работает в 3 раза быстрее чем стандартная move
Да хз. Мне кажется не будет в 3 раза. Всё-таки многое упирается в кеш. Ну максимум процентов на 20-50
Хотя чем чёрт не шутит, надо смотреть. Я лишь знаю, что SSE, да в моём исполнении, должен дать прирост

vavanможно старые бенчи от FastCode поднять
а потом все равно придет шарахов и всех разгонит
Можно
Я за
Сделай если не лень

asviridenkovКстати, раз ты такой фанат оптимизации... У меня, по опыту, на общую производительность влияет больше не move а функция поиска символа в строке. Ниже приведу текущую реализацию. Слабо улучшить?
Фу, ацтой!
Есть же подходы, позволяющие найти символ в одной из частей регистра. Но в твоём случае надо будет придумать с выравниванием и SSE
Посмотри здесь: http://www.wasm.ru/forum/viewtopic.php?id=16358&p=1
А в тырнете поищи константы 7EFEFEFF и 81010100
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779041
Artem_Nav
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Я бы не заморачивался особо. По следующей причине :
А недавно вдруг раз, и все вернулось на круги своя. (Может быть, для того, чтобы не заставлять переписывать memcpy на AVX?) Для последних процессоров классическая реализация memcpy снова самая быстрая. Так что если кто-то проспал 34 года, самое время вытащить старый код, и победно посмотреть на коллег, которые переписывали memcpy последовательно на MMX, SSE2, SSE3, SSE4.1.

Думаю, вскоре и AMD подтянется, а через пару-тройку лет все велосипеды будут не нужны вообще :)
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779050
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Artem_NavЯ бы не заморачивался особо. По следующей причине:
Думаю, вскоре и AMD подтянется, а через пару-тройку лет все велосипеды будут не нужны вообще :)
Поживём-увидим
Я лишь исхожу из того, что актуально сегодня

All ,
Слушайте!
Я сейчас почитал, для архитектуры x64 нельзя свободно использовать xmm6 и xmm7 - их надо сохранять на стеке
Сейчас исправлю

А есть такие же ограничения для x86?
По моему нет
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779059
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUКак ты предлагаешь Zero-Copy вставить в Move? :)
Посредством Ctrl-Y. Если твой алгоритм требует использования Move(), значит это
неправильный алгоритм.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779066
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В общем функция отныне выглядит так (добавлено сохранение/восстановление xmm6 и xmm7 в случае необходимости):
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
// SSE-based non-collision Move() realization
procedure NonCollisionMove(const Source; var Dest; const Size: NativeUInt);
asm
  // basic routine
  {$ifdef CPUX86}
    cmp ecx, 32
  {$else .CPUX64}
    cmp r8, 32
    // make Source = eax/rax, Dest = edx/rdx, Size = ecx/rcx
    mov rax, rcx
    xchg rcx, r8
    // r9 as pointer to @move_03_items
    lea r9, [@move_03_items]
  {$endif}

  // is big/large (32...inf)
  jae @move_big

  // is small (0..3)
  cmp ecx, 4
  jb @move_03

  // move middle(4..31) = move 16(0..16) + move dwords(0..12) + move small(0..3)
  cmp ecx, 16
  jb @move_015

  {$ifdef CPUX86}
    movups xmm0, [eax]
    movups [edx], xmm0
    jne @move_015_offset
    ret
  @move_015_offset:
    sub ecx, 16
    add eax, 16
    add edx, 16
  @move_015:
    push ecx
    and ecx, -4
    add eax, ecx
    add edx, ecx
    jmp [ecx + @move_dwords]
    @move_dwords: DD @rw_0,@rw_4,@rw_8,@rw_12
    @rw_12:
      mov ecx, [eax-12]
      mov [edx-12], ecx
    @rw_8:
      mov ecx, [eax-8]
      mov [edx-8], ecx
    @rw_4:
      mov ecx, [eax-4]
      mov [edx-4], ecx
    @rw_0:
    pop ecx
    and ecx, 3
  {$else .CPUX64}
    movups xmm0, [rax]
    movups [rdx], xmm0
    jne @move_015_offset
    ret
  @move_015_offset:
    sub rcx, 16
    add rax, 16
    add rdx, 16
  @move_015:
    // make r9 = dest 0..3 pointer, rcx = dwords count
    mov r8, rcx
    shr rcx, 2
    and r8, 3
    lea r9, [r9 + r8*8]
    // case jump
    lea r8, [@move_dwords]
    jmp qword ptr [r8 + rcx*8]
    @move_dwords: DQ @rw_0,@rw_4,@rw_8,@rw_12
    @rw_8:
      mov rcx, [rax]
      mov [rdx], rcx
      add rax, 8
      add rdx, 8
    jmp qword ptr [r9]
    @rw_12:
      mov rcx, [rax]
      mov [rdx], rcx
      add rax, 8
      add rdx, 8
    @rw_4:
      mov ecx, [rax]
      mov [rdx], ecx
      add rax, 4
      add rdx, 4
    @rw_0:  
    jmp qword ptr [r9]
  {$endif}

@move_03:
  {$ifdef CPUX86}
    jmp [offset @move_03_items + ecx*4]
    @move_03_items: DD @0,@1,@2,@3
    @2: mov cx, [eax]
        mov [edx], cx
        ret
    @3: mov cx, [eax]
        mov [edx], cx
        add eax, 2
        add edx, 2
    @1: mov cl, [eax]
        mov [edx], cl
    @0: ret
  {$else .CPUX64}
    jmp qword ptr [r9 + rcx*8]
    @move_03_items: DQ @0,@1,@2,@3
    @2: mov cx, [rax]
        mov [rdx], cx
        ret
    @3: mov cx, [rax]
        mov [rdx], cx
        add rax, 2
        add rdx, 2
    @1: mov cl, [rax]
        mov [rdx], cl
    @0: ret
  {$endif}

@move_big:
  {$ifdef CPUX86}
    cmp ecx, 16*4
  {$else .CPUX64}
    cmp rcx, 16*4
  {$endif}
  jae @move_large
  
  // big memory move by SSE (32..63) = (32..48) + (0..15)
  {$ifdef CPUX86}
     test ecx, 15
     jz @move_32_48

     push ecx
     and ecx, 15
     movups xmm0, [eax]
     movups [edx], xmm0
     add eax, ecx
     add edx, ecx

     pop ecx
     and ecx, -16
  {$else .CPUX64}
     mov r8, rcx
     test rcx, 15
     jz @move_32_48

     and r8, 15
     movups xmm0, [rax]
     movups [rdx], xmm0
     add rax, r8
     add rdx, r8

     and rcx, -16
  {$endif}

@move_32_48:
  {$ifdef CPUX86}
    add eax, ecx
    add edx, ecx
    cmp ecx, 48
    jb @rw_32
    @rw_48: movups xmm2, [eax - 2*16 - 16]
            movups [edx - 2*16 - 16], xmm2
    @rw_32: movups xmm1, [eax - 1*16 - 16]
            movups xmm0, [eax - 0*16 - 16]
            movups [edx - 1*16 - 16], xmm1
            movups [edx - 0*16 - 16], xmm0
  {$else .CPUX64}
    add rax, rcx
    add rdx, rcx
    cmp rcx, 48
    jb @rw_32    
    @rw_48: movups xmm2, [rax - 2*16 - 16]
            movups [rdx - 2*16 - 16], xmm2
    @rw_32: movups xmm1, [rax - 1*16 - 16]
            movups xmm0, [rax - 0*16 - 16]
            movups [rdx - 1*16 - 16], xmm1
            movups [rdx - 0*16 - 16], xmm0
  {$endif}

  ret
@move_large:
  // large memory move by SSE (64..inf)

  // destination alignment
  {$ifdef CPUX86}
    push ebx
    test edx, 15
    jz @move_16128_initialize

    mov ebx, edx
    movups xmm0, [eax]
    movups [ebx], xmm0

    add edx, 15
    and edx, -16
    sub ebx, edx
    sub eax, ebx
    add ecx, ebx
  {$else .CPUX64}
    test rdx, 15
    jz @move_16128_initialize

    mov r8, rdx
    movups xmm0, [rax]
    movups [r8], xmm0

    add rdx, 15
    and rdx, -16
    sub r8, rdx
    sub rax, r8
    add rcx, r8
  {$endif}

@move_16128_initialize:
  {$ifdef CPUX86}
    push ecx
    mov ebx, offset @aligned_reads
    shr ecx, 4
    test eax, 15
    jz @move_16128
    mov ebx, offset @unaligned_reads
  {$else .CPUX64}
    movaps [rsp-8-16], xmm6
    movaps [rsp-8-32], xmm7
    mov r8, rcx
    lea r9, [@aligned_reads]
    shr rcx, 4
    test rax, 15
    jz @move_16128
    lea r9, [@unaligned_reads]
  {$endif}

@move_16128:
  {$ifdef CPUX86}
    cmp ecx, 8
    jae @move_128

    lea ecx, [ecx + ecx]
    lea eax, [eax + ecx*8]
    lea edx, [edx + ecx*8]
    lea ebx, [ebx + 8*4]
    neg ecx
    lea ebx, [ebx + ecx*2]
    jmp ebx
  @move_128:
    lea eax, [eax + 128]
    lea edx, [edx + 128]
    lea ecx, [ecx - 8]
    jmp ebx
  {$else .CPUX64}
    cmp rcx, 8
    jae @move_128

    lea rcx, [rcx + rcx]
    lea rax, [rax + rcx*8]
    lea rdx, [rdx + rcx*8]
    lea r9, [r9 + 8*4]
    neg rcx
    lea r9, [r9 + rcx*2]
    jmp r9
  @move_128:
    lea rax, [rax + 128]
    lea rdx, [rdx + 128]
    lea rcx, [rcx - 8]
    jmp r9
  {$endif}

  // aligned sse read
  @aligned_reads:
  {$ifdef CPUX86}
    movaps xmm7, [eax - 7*16 - 16]
    movaps xmm6, [eax - 6*16 - 16]
    movaps xmm5, [eax - 5*16 - 16]
    movaps xmm4, [eax - 4*16 - 16]
    movaps xmm3, [eax - 3*16 - 16]
    movaps xmm2, [eax - 2*16 - 16]
    movaps xmm1, [eax - 1*16 - 16]
    movaps xmm0, [eax - 0*16 - 16]
  {$else .CPUX64}
    movaps xmm7, [rax - 7*16 - 16]
    movaps xmm6, [rax - 6*16 - 16]
    movaps xmm5, [rax - 5*16 - 16]
    movaps xmm4, [rax - 4*16 - 16]
    movaps xmm3, [rax - 3*16 - 16]
    movaps xmm2, [rax - 2*16 - 16]
    movaps xmm1, [rax - 1*16 - 16]
    movaps xmm0, [rax - 0*16 - 16]
  {$endif}
  jae @aligned_writes
  jmp @write_16112

  // unaligned sse read
  @unaligned_reads:
  {$ifdef CPUX86}
    movups xmm7, [eax - 7*16 - 16]
    movups xmm6, [eax - 6*16 - 16]
    movups xmm5, [eax - 5*16 - 16]
    movups xmm4, [eax - 4*16 - 16]
    movups xmm3, [eax - 3*16 - 16]
    movups xmm2, [eax - 2*16 - 16]
    movups xmm1, [eax - 1*16 - 16]
    movups xmm0, [eax - 0*16 - 16]
    jae @aligned_writes
  @write_16112:
    lea ebx, [offset @aligned_writes + 8*4 + ecx*2]
    jmp ebx
  {$else .CPUX64}
    movups xmm7, [rax - 7*16 - 16]
    movups xmm6, [rax - 6*16 - 16]
    movups xmm5, [rax - 5*16 - 16]
    movups xmm4, [rax - 4*16 - 16]
    movups xmm3, [rax - 3*16 - 16]
    movups xmm2, [rax - 2*16 - 16]
    movups xmm1, [rax - 1*16 - 16]
    movups xmm0, [rax - 0*16 - 16]
    jae @aligned_writes
  @write_16112:
    lea r9, [@aligned_writes + 8*4]
    lea r9, [r9 + rcx*2]
    jmp r9
  {$endif}

  // aligned sse write, loop
  @aligned_writes:
  {$ifdef CPUX86}
    movaps [edx - 7*16 - 16], xmm7
    movaps [edx - 6*16 - 16], xmm6
    movaps [edx - 5*16 - 16], xmm5
    movaps [edx - 4*16 - 16], xmm4
    movaps [edx - 3*16 - 16], xmm3
    movaps [edx - 2*16 - 16], xmm2
    movaps [edx - 1*16 - 16], xmm1
    movaps [edx - 0*16 - 16], xmm0
    test ecx, ecx
  {$else .CPUX64}
    movaps [rdx - 7*16 - 16], xmm7
    movaps [rdx - 6*16 - 16], xmm6
    movaps [rdx - 5*16 - 16], xmm5
    movaps [rdx - 4*16 - 16], xmm4
    movaps [rdx - 3*16 - 16], xmm3
    movaps [rdx - 2*16 - 16], xmm2
    movaps [rdx - 1*16 - 16], xmm1
    movaps [rdx - 0*16 - 16], xmm0
    test rcx, rcx
  {$endif}
  jg @move_16128

  // last 0..15 bytes
  {$ifdef CPUX86}
    pop ecx
    pop ebx
    and ecx, 15
    jnz @move_115
    ret
  @move_115:
    add eax, ecx
    add edx, ecx
    movups xmm0, [eax - 0*16 - 16]
    movups [edx - 0*16 - 16], xmm0
  {$else .CPUX64}
    movaps xmm6, [rsp-8-16]
    movaps xmm7, [rsp-8-32]
    and r8, 15
    jnz @move_115
    ret
  @move_115:
    add rax, r8
    add rdx, r8
    movups xmm0, [rax - 0*16 - 16]
    movups [rdx - 0*16 - 16], xmm0
  {$endif}
end;
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779068
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry SibiryakovПосредством Ctrl-Y. Если твой алгоритм требует использования Move(), значит это
неправильный алгоритм.
Дим, мне кажется ты сегодня слишком устал :)
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779267
asviridenkov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

Раз ты с SSE разбирался, скажи pls, есть возможность умножить 4-ре 32 битных числа в одном xmm регистре на одно 32-битное число, с возвратом младших 32 бит результата.
Ну или хотя-бы одной командой загрузить 32-битное число в xmm регистр с дублированием, как 4 32-битных числа. Потому как умножение одного xmm на другой я нашел, но без первых команд в нем смысла мало.
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779271
TVoid
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
SOFT FOR YOU,
Все знают, какой я фанат оптимизаций, и вместе с тем нелюбитель писать тесты замера скорости
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
var
 mSize    :NativeUInt;
 tResult  :string;

procedure Test(aVoid:Pointer);
var 
 i,n:NativeInt;
 t:Cardinal;
 pA,pB:Pointer;
 zA,zB:PNativeInt;
begin
 tResult := 'Error?!';

 pA := GetMemory(mSize); // VirtualAlloc(nil,mSize,MEM_COMMIT or MEM_RESERVE,PAGE_READWRITE);//
 pB := GetMemory(mSize); // VirtualAlloc(nil,mSize,MEM_COMMIT or MEM_RESERVE,PAGE_READWRITE);//

 if (pA <> nil) and (pB <> nil) then begin
 
  n := mSize div sizeOf(zA^) - 1;

  t := GetTickCount();
   zA := pA; for i := 0 to n do begin zA^:=i; inc(zA);  end;
   zB := pB; for i := 0 to n do begin zB^:=i; inc(zB);  end;
  t := GetTickCount() - t; 
  tResult := IntToStr(mSize div (1024*1024))+'::'#9'Zz ' + IntToStr(t);

  
  t := GetTickCount();
   NonCollisionMove(pA^,pB^,mSize);
  t := GetTickCount() - t; 
  tResult :=tResult + #9'Na ' + IntToStr(t);

  t := GetTickCount();
   Move(pA^,pB^,mSize);
  t := GetTickCount() - t; 
  tResult := tResult + #9'Ma ' + IntToStr(t);

  t := GetTickCount();
   NonCollisionMove(pA^,pB^,mSize);
  t := GetTickCount() - t; 
  tResult := tResult + #9'Nb ' + IntToStr(t);

  t := GetTickCount();
   Move(pA^,pB^,mSize);
  t := GetTickCount() - t; 
  tResult := tResult + #9'Mb ' + IntToStr(t);  

  t := GetTickCount();
   NonCollisionMove(pA^,pB^,mSize);
  t := GetTickCount() - t; 
  tResult :=tResult + #9'Nc ' + IntToStr(t);
  
  
 end;
 FreeMemory(pB); // VirtualFree(pB,0,MEM_RELEASE); // 
 FreeMemory(pA); // VirtualFree(pA,0,MEM_RELEASE); //  
 
 SendMessage(Form1.Handle,WM_USER,0,0); 
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
 Caption := 'Go...';  
 Button1.Enabled := False;
 mSize := StrToInt64Def(Edit1.Text,512)*(1024*1024);
 CloseHandle(BeginThread(nil,0,@Test,nil,0,PCardinal(nil)^));
end;

procedure TForm1.WmUser(var Message: TMessage);
begin
 Caption := 'SuperTest!';
 Memo1.Lines.Add(tResult);
 Button1.Enabled := True;
end;



https://yadi.sk/d/k1AkSVvLc5csC
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779287
asviridenkov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
TVoid,

Получается выигрыш от силы 10%
То есть в целом приложении вообще его не будет
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779390
vavan
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUСделай если не леньлень конечно, для моих задач меня вполне устраивают фасткодерские
плюс на просторах в ассортименте прочих от интела, vs и т.п. если уж реально упираешься в move
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779393
vavan
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vavanдля моих задач меня вполне устраивают фасткодерскиено твои инициативы всегда приветствую!
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779395
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
Пока я бухал спал, тут такая тема вчера была ))
Скачал тесты, преимущество максимум в несколько процентов.
Решил, что фтопку такие портянки, оно того не стоит.
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779455
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
asviridenkovРаз ты с SSE разбирался, скажи pls, есть возможность умножить 4-ре 32 битных числа в одном xmm регистре на одно 32-битное число, с возвратом младших 32 бит результата.
Ну или хотя-бы одной командой загрузить 32-битное число в xmm регистр с дублированием, как 4 32-битных числа. Потому как умножение одного xmm на другой я нашел, но без первых команд в нем смысла мало.
Да я не особо в SSE разбирался
Но для твоего случае используют SHUFPS
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779502
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
TVoid

Оу, спасибо за первый тест!
1) Я не понял, ты 5Гб ОЗУ копируешь?
2) У тебя a и b варианты не отличаются. Что за Nc?
3) Тесты у тебя упираются в кеш, и скорее всего в файл подкачки
4) Тестировать мне кажется нужно на трёх компиляторах: новый Delphi x86, новый Delphi x64, старый Delphi (где не FastCode реализация). Для каждого из этих случаев скорость копирования будет отличаться
5) Я посмотрел исходники Move, там идёт разделение градаций размера на 1..8, 9..32 и 32+. У меня немного другие. Думаю тестировать копирование больше, чем 100кб - смысла нет. Ну и потом нафига тестировать кеш-мисс, если на практике данные преимущественно в кеше, ну как минимум 3 уровня. Поэтому для размера предлагаю такую вот штуку:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
function RandomSize(): NativeInt;
begin
  case Random(4) of
    0: Result := 1 + Random(8-1+1); // 1..8
    1: Result := 9 + Random(32-9+1); // 9..32
    2: Result := 33 + Random(128-33+1); // 33..128
  else
    Result := Random(100*1024);
  end;
end;


6) Ну а для замера времени предлагаю устроить цикл. Причём обусловиться, что за замеряемое время копируется константный объём памяти, например порядка 1Гб
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
const
  MOVE_SIZE = 1 * 1024*1024*1024;

procedure RunMove(const Source; var Dest; const Size: NativeInt);
var
  i: Integer;
begin
  for i := 1 to MOVE_SIZE div Size do
  System.Move(Source, Dest, Size);
end;

procedure RunNcMove(const Source; var Dest; const Size: NativeInt);
var
  i: Integer;
begin
  for i := 1 to MOVE_SIZE div Size do
  NonCollisionMove(Source, Dest, Size);
end;


7) Ещё бы неплохо поиграться с выравниванием, как для Source, так и для Dest. Для стандартной реализации важно выравнивание на 8 байт, для моей - 16.
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779504
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorСкачал тесты, преимущество максимум в несколько процентов.
Решил, что фтопку такие портянки, оно того не стоит.
Твои выводы обусловлены неумением анализировать ситуацию :)
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779572
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
SOFT FOR YOUdefecatorСкачал тесты, преимущество максимум в несколько процентов.
Решил, что фтопку такие портянки, оно того не стоит.
Твои выводы обусловлены неумением анализировать ситуацию :)

Да мне пофигу, я стараюсь память не копировать с места на место, а обходиться указателями.
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779584
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorДа мне пофигу, я стараюсь память не копировать с места на место, а обходиться указателями.
Да ты как бы не новатор :)
Но порой не получается обойтись без Move. Это и операции со строками, и динамическими массивами, и в моём случае запись/чтение данных в "стрим"
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779595
Фотография defecator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Модератор форума
SOFT FOR YOUdefecatorДа мне пофигу, я стараюсь память не копировать с места на место, а обходиться указателями.
Да ты как бы не новатор :)
Но порой не получается обойтись без Move. Это и операции со строками, и динамическими массивами, и в моём случае запись/чтение данных в "стрим"

Я разрабатываю, по большей части, проекты, которые должны работать 24x7x365,
и использование там типов String, динамических массивов и всяких SetLength категорически выкидывается.
...
Рейтинг: 0 / 0
Ну очень быстрый Move() для x86/x64
    #38779605
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
defecatorЯ разрабатываю, по большей части, проекты, которые должны работать 24x7x365,
и использование там типов String, динамических массивов и всяких SetLength категорически выкидывается.
Я буду тебе благодарен, если ты избавишь нас от подробностей своей личной жизни
Спасибо :)
...
Рейтинг: 0 / 0
25 сообщений из 38, страница 1 из 2
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Ну очень быстрый Move() для x86/x64
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]