-
Notifications
You must be signed in to change notification settings - Fork 0
/
Справка OpenCLABC.html
1910 lines (1704 loc) · 116 KB
/
Справка OpenCLABC.html
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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="Common/0.css" />
</head>
<body>
<div class="page-base" id="page-select">
<div class="page-container" id="page-select-container"></div>
</div>
<div class="page-base" id="page-display">
<div class="page-container" id="page-display-container">
<div id="page-display-container-body"></div>
</div>
</div>
<div class="page-base" id="splitter"></div>
<script src="Common/0.js" ></script>
<script>on_start_folder("CL ABC", null)</script>
<script>on_start_folder("Общие сведения", null)</script>
<div id="page-1" page_name="О справке" hidden=true>
<p>Данная справка относится к модулю <code>OpenCLABC</code>, входящему в состав стандартных модулей языка <code>PascalABC.NET</code>.</p>
<p>Модуль <code>OpenCLABC</code> это высокоуровневая обёртка модуля <code>OpenCL</code>.<br />
Это значит, что с <code>OpenCLABC</code> можно писать гораздо меньше кода в больших и сложных программах,
однако такой же уровень микроконтроля как с модулем <code>OpenCL</code> недоступен.
Например, напрямую управлять <code>cl_event</code>'ами в <code>OpenCLABC</code> невозможно.
Вместо этого надо использовать операции с очередями (например, сложение и умножение очередей)</p>
<p>Справка модуля <code>OpenCL</code> отсутствует. Вместо неё смотрите:</p>
<ul>
<li><a href="%D0%93%D0%B0%D0%B9%D0%B4%20%D0%BF%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E%20OpenGL%20%D0%B8%20OpenCL.html">Общий гайд по использованию модулей <code>OpenGL</code> и <code>OpenCL</code></a></li>
<li><a href="https://www.khronos.org/registry/OpenCL/">Контейнер справок библиотеки <code>OpenCL</code>, на которой основан модуль <code>OpenCL</code></a></li>
</ul>
<p>Если в справке или модуле найдена ошибка, или чего-либо не хватает - пишите в <a href="https://github.com/SunSerega/POCGL/issues">issue</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-1"))</script>
<div id="page-2" page_name="Термины, которые часто путают новички" hidden=true>
<ul>
<li><p>CPU — Центральное Процессорное Устройство (процессор);</p>
</li>
<li><p>GPU — Графическое Процессорное Устройство (видеокарта);</p>
</li>
<li><p>RAM — Оперативная память;</p>
</li>
<li><p>Команда — запрос на выполнение чего-либо. К примеру:</p>
<ul>
<li>Запрос на запуск программы на GPU;</li>
<li>Запрос на начало чтения данных из памяти GPU в оперативную память;</li>
</ul>
<p><strong>Называть процедуры и функции командами ошибочно!</strong></p>
</li>
<li><p>Подпрограмма — процедура или функция;</p>
</li>
<li><p>Метод — особая подпрограмма, вызываемая через экземпляр:</p>
<ul>
<li>К примеру, метод <code>Context.SyncInvoke</code> выглядит в коде как <code>cont.SyncInvoke(...)</code>, где <code>cont</code> — переменная типа <code>Context</code>;</li>
</ul>
</li>
</ul>
<p>Остальные непонятные термины можно найти в справке <code>PascalABC.NET</code> или в интернете.</p>
</div>
<script>on_page_added(document.getElementById("page-2"))</script>
<script>on_end_folder()</script>
<div id="page-3" page_name="" hidden=true>
<p>Сам модуль <code>OpenCLABC</code> - обёртка модуля <code>OpenCL</code>. Это значит, что внутри он использует
содержимое <code>OpenCL</code>, но при подключении - показывает только свой личный функционал.</p>
<p>Так же множество типов из <code>OpenCLABC</code> являются обёртками типов из <code>OpenCL</code>.</p>
<p>Тип <code>CommandQueue</code> использует тип <code>cl_command_queue</code>, но предоставляет очень много не связанного с <code>cl_command_queue</code> функционала.</p>
<p>"Простые" типы-обёртки модуля <code>OpenCLABC</code> предоставляют только функционал соответствующего
типа из модуля <code>OpenCL</code>... в более презентабельном виде. Из общего - у таких типов есть:</p>
<ul>
<li><p>Свойство <code>.Native</code>, возвращающее внутренний неуправляемый объект из модуля <code>OpenCL</code>.<br />
Если вам не пришлось, по какой-либо причине, использовать <code>OpenCLABC</code> и <code>OpenCL</code> вместе - это свойство может понадобится только для дебага.</p>
</li>
<li><p>Свойство <code>.Properties</code>, возвращающее объект свойств внутреннего объекта.<br />
Свойства неуправляемого объекта никак не обрабатываются и не имеют описаний.
Но типы этих свойств всё равно преобразуются в управляемые (особо заметно на строках и массивах).</p>
</li>
</ul>
<hr />
<h1>Страницы:</h1>
<ul>
<li><a path="Platform"> Platform </a> - Платформа, объединяющая несколько совместимых устройств;</li>
<li><a path="Device"> Device </a> - Устройство, поддерживающее OpenCL;</li>
<li><a path="Context"> Context </a> - Контекст выполнения;</li>
<li><a path="Kernel"> Kernel </a> - Подпрограмма, выполняемая на GPU;</li>
<li><a path="ProgramCode"> ProgramCode </a> - Контейнер kernel'ов;</li>
<li><a path="Память"> Память </a> - Обёртки памяти GPU.</li>
</ul>
</div>
<script>on_start_folder("Простые обёртки", document.getElementById("page-3"))</script>
<div id="page-4" page_name="Platform" hidden=true>
<p>В списке устройств контекста могут быть только совместимые друг с другом устройства.<br />
Коллекция совместимых устройств называется платформой и хранится в объектах типа <code>Platform</code>.</p>
<hr />
<p>Обычно платформы получают из статического свойства <code>Platform.All</code>:</p>
<pre><code>## uses OpenCLABC;
var pls := Platform.All;
pls.PrintLines;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-4"))</script>
<div id="page-5" page_name="Device" hidden=true>
<p>Обычно устройства получают статическим методом <code>Device.GetAllFor</code>:</p>
<pre><code>## uses OpenCLABC;
foreach var pl in Platform.All do
begin
Writeln(pl);
var dvcs := Device.GetAllFor(pl, DeviceType.DEVICE_TYPE_ALL);
if dvcs<>nil then dvcs.PrintLines;
Writeln('='*30);
end;
</code></pre>
<p>И в большинстве случаев - это всё что вам понадобится.</p>
<hr />
<p>Но если где то нужен более тонкий контроль - можно создать несколько виртуальных
под-устройств, каждому из которых даётся часть ядер изначального устройства.<br />
Для этого используются методы <code>.Split*</code>:</p>
<pre><code>## uses OpenCLABC;
var dvc := Context.Default.MainDevice;
Writeln('Поддерживаемые типы .Spilt-ов:');
var partition_properties := dvc.Properties.PartitionProperties;
if (partition_properties.Length=0) or (partition_properties[0].val = System.IntPtr.Zero) then
begin
Writeln('Ничего не поддерживается...');
exit;
end else
partition_properties.PrintLines;
Writeln('='*30);
Writeln('Виртуальные устройства, по 1 ядру каждое:');
if dvc.CanSplitEqually then
// Если упадёт потому что слишком много
// устройств - пожалуйста, напишите в issue
dvc.SplitEqually(1).PrintLines else
Writeln('Не поддерживается...');
Writeln('='*30);
Writeln('Два устройства, 1 и 2 ядра соответственно:');
if dvc.CanSplitByCounts then
dvc.SplitByCounts(1,2).PrintLines else
Writeln('Не поддерживается...');
Writeln('='*30);
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-5"))</script>
<div id="page-6" page_name="Context" hidden=true>
<p>Контекст (объект типа <code>Context</code>) содержит информацию об устройствах,
на которых надо выделяет области памяти <code>OpenCL</code> и выполнять код для GPU.</p>
<hr />
<p>Создать контекст можно конструктором (<code>new Context(...)</code>).<br />
Контекст можно и не создавать, используя везде свойство <code>Context.Default</code>.</p>
<p>Если в операции, требующей контекст (как выделение памяти GPU),
его не указать - будет автоматически выбран <code>Context.Default</code>.<br />
Неявные очереди всегда используют <code>Context.Default</code>.</p>
<p>Изначально этому свойству присваивается контекст, использующий GPU, если оно имеется,
или любое другое устройство, поддерживающее OpenCL, если GPU отсутствует.</p>
<p>Если устройств поддерживающих <code>OpenCL</code> нет - <code>Context.Default</code> будет <code>nil</code>.<br />
Однако такая ситуация на практике невозможна, потому что OpenCL поддерживается
практически всеми современными устройствами, занимающимися выводом изображения на экран.<br />
Если <code>Context.Default = nil</code> - переустановите графические драйверы.</p>
<hr />
<p><code>Context.Default</code> можно перезаписывать.<br />
Используйте эту возможность только если во всей программе нужен общий контекст, но не стандартный.</p>
<p>Если ваша программа достаточно сложная чтобы нуждаться в нескольких контекстах - лучше не использовать
<code>Context.Default</code>. И присвоить ему <code>nil</code>, чтобы не использовать случайно (к примеру, неявной очередью).</p>
</div>
<script>on_page_added(document.getElementById("page-6"))</script>
<div id="page-7" page_name="Kernel" hidden=true>
<p>Объект типа <code>Kernel</code> представляет одну подпрограмму в OpenCL-C коде,
объявленную с ключевым словом <code>__kernel</code>.</p>
<hr />
<p>Обычно <code>Kernel</code> создаётся через индексное свойтсво <code>ProgramCode</code>:</p>
<pre><code>var code: ProgramCode;
...
var k := code['KernelName'];
</code></pre>
<p>Тут <code>'KernelName'</code> — имя подпрограммы-kernel'а в исходном коде (регистр важен!).</p>
<hr />
<p>Так же можно получить список всех kernel'ов объекта <code>ProgramCode</code>, методом <code>ProgramCode.GetAllKernels</code>:</p>
<pre><code>var code: ProgramCode;
...
var ks := code.GetAllKernels;
ks.PrintLines;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-7"))</script>
<div id="page-8" page_name="ProgramCode" hidden=true>
<p>Обычные программы невозможно запустить на GPU. Для этого надо писать особые программы.<br />
В контексте OpenCL - эти программы обычно пишутся на языке "OpenCL C" (основанном на языке "C").</p>
<p>Язык OpenCL-C это часть библиотеки OpenCL, поэтому его справку можно найти <a href="https://www.khronos.org/registry/OpenCL/">там же</a>, где и справку OpenCL.</p>
<p>В <code>OpenCLABC</code> OpenCL-C код хранится в объектах типа <code>ProgramCode</code>.<br />
Объекты этого типа используются только как контейнеры.
Один объект ProgramCode может содержать любое количествово подпрограмм-kernel'ов.</p>
<hr />
<p>Самый простой способ создать <code>ProgramCode</code> - конструктором:</p>
<pre><code>var code := new ProgramCode(
ReadAllText('file with code 1.cl'),
ReadAllText('file with code 2.cl')
);
</code></pre>
<p><strong>Внимание</strong>! Этот конструктор принимает именно тексты исходников, не имена файлов.<br />
Если надо передать текст из файла - его надо сначала явно прочитать.</p>
<hr />
<p>Так же как исходники паскаля хранят в .pas файлах, исходники OpenCL-C кода хранят в .cl файлах.</p>
<p>На самом деле это не обязательно, потому что код даже не обязан быть в файле:</p>
<pre><code>var code_text := '__kernel void k() {}';
var code := new ProgramCode(code_text);
</code></pre>
<p>Так как конструктор <code>ProgramCode</code> принимает текст - исходники программы
на языке OpenCL-C можно хранить даже в строке в .pas программе.</p>
<p>Тем не менее, хранить исходники OpenCL-C кода в .cl файлах обычно удобнее всего.</p>
<hr />
<p>После создания объекта типа <code>ProgramCode</code> из исходников можно вызвать
метод <code>ProgramCode.SerializeTo</code>, чтобы сохранить код в бинарном и прекомпилированном виде.</p>
<p>Затем, объект <code>ProgramCode</code> можно пере-создать статический метод <code>ProgramCode.DeserializeFrom</code>.</p>
<p>Пример можно найти в папке примеров <code>Прекомпиляция ProgramCode</code> или <a href="https://github.com/SunSerega/POCGL/tree/master/Samples/OpenCLABC/%D0%9F%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%86%D0%B8%D1%8F%20ProgramCode">тут</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-8"))</script>
<div id="page-9" page_name="Память" hidden=true>
<p>Типы-простые_обёртки памяти OpenCL (<code>cl_mem</code>) рассмотрены в
<a path="../KernelArg/Память OpenCL (CL-)/">этой папке</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-9"))</script>
<script>on_end_folder()</script>
<div id="page-10" page_name="" hidden=true>
<p>Методы, запускающие <code>Kernel</code> принимают специальные аргументы типа <code>KernelArg</code>, которые передаются в OpenCL-C код.<br />
Экземпляр <code>KernelArg</code> может быть создан из нескольких типов значений, а точнее:</p>
<pre><code>## uses OpenCLABC;
var k: Kernel;
var val1 := 3;
var val2 := 5;
var val3 := new NativeValue<byte>(7);
var a: array of byte;
k.Exec1(1,
// Передавать можно:
// Области памяти
new CLArray<byte>(1),
new MemorySegment(1),
// Очереди возвращающие область памяти
HFQ(()->new MemorySegment(1)),
// В том числе CLArrayCCQ и т.п.
CLArray&<byte>.Create(1).NewQueue,
// Указатель на размерное значение
// (в kernel попадёт само значение, не указатель)
@val2,
// Так нельзя, потому что val1 была захвачена лямбдой из HFQ ниже
// @val1,
// Расширенный набор параметров для передачи адреса
KernelArg.FromData(new System.IntPtr(@val2), new System.UIntPtr(sizeof(integer))),
// Размерное значение
val1,
HFQ(()->val1),
// В том числе неуправляемое
val3,
HFQ(()->val3),
// Массив размерных значений
a,
HFQ(()->a)
);
</code></pre>
<p>Обратите внимание, <code>KernelArg</code> из указателя на <code>val2</code> будет немного эффективнее
чем <code>KernelArg</code> из самого значения <code>val2</code>. Но эту возможность стоит использовать
только как тонкую оптимизацию, потому что много чего может пойти не так.
Если передавать <code>@val2</code> в качестве <code>KernelArg</code> - надо знать все тонкости.</p>
<hr />
<p>Если <code>@x</code> передали вкачестве <code>KernelArg</code>:</p>
<ol>
<li><p><code>x</code> не может быть глобальной переменной или полем класса:</p>
<pre><code>uses OpenCLABC;
type
t1 = class
val1 := 1; // Не подходит, потому что поле класса
static val2 := 2; // И статических полей это тоже касается
end;
var
val3 := 3; // Глобальные переменные - тоже статические поля
k: Kernel; // k не важно где объявлена
procedure p1;
// Теоретически подходит, но вообще это плохой стиль кода
var val4 := 4;
begin
// Однозначно подходит, потому что объявлена не только в той же
// подпрограмме, но и прямо перед использованием в k.Exec*
var val5 := 5;
k.Exec1(1,
// Это единственные 2 переменные, которые можно передавать адресом
@val4,
@val5
);
end;
begin end.
</code></pre>
</li>
<li><p><code>x</code> не должно быть захвачего лямбдой.<br />
Хотя указатель на <code>x</code> уже можно захватывать:</p>
<pre><code>## uses OpenCLABC;
var k: Kernel;
var val1 := 3;
// На val1 всё ещё накладываются все ограничения,
// когда val1_ptr использована в качестве KernelArg
// Но к самой val1_ptr эти ограничения не применяются
var val1_ptr := @val1;
k.Exec1(1,
val1,
HFQ(()->val1_ptr^), // Захватили переменную val1_ptr, а не val1
// val1 нигде не захвачена, поэтому теперь так можно
@val1,
val1_ptr // То же самое
);
</code></pre>
</li>
<li><p>Выходить из подпрограммы, где объявили <code>x</code> нельзя, пока <code>.Exec</code> не закончит выполнятся.
Это так же значит, что возвращать очередь, содержащую <code>KernelArg</code> из <code>@x</code> обычно нельзя.<br />
Но это можно обойти, если объявлять переменную в другой подпрограмме:</p>
<pre><code>uses OpenCLABC;
var k: Kernel; // Вообще лучше передавать параметром в p2
function p2(var val: integer): CommandQueueBase;
begin
Result := k.NewQueue.AddExec1(1,
@val
);
end;
procedure p1;
begin
var val: integer;
var q := p2(val);
// Опять же, q не должна продолжать выпоняться
// после выхода из p1, потому что тут объявлена val
Context.Default.SyncInvoke(q);
end;
begin
p1;
end.
</code></pre>
<p>Тут <code>val</code> объявлена в <code>p1</code>. При этом <code>val</code> в <code>p2</code> является синонимом <code>val</code> из <code>p1</code>,
потому что объявлена <code>var</code>-параметром - передачей по ссылке.<br />
Если передать без <code>var</code> перед параметром - <code>val</code> из <code>p2</code> будет копией,
а значит перестанет существовать после выхода из <code>p2</code>.</p>
</li>
</ol>
<p>Обратите внимание: Компилятор не заставит вас следовать описанным тут ограничениям.
И показанные на этой странице коды могут работать не смотря на эти ограничения,
потому что саму область памяти, на которую ссылается созданный <code>KernelArg</code>, никто никогда не уничтожает.</p>
<p>Но в то же время эту же область памяти может использовать под другие данные, таким образом
заменяя значение которое вы пытались передать в GPU на мусорные данные.</p>
<p>Это плохо в первую очередь потому, что на перезапись этих данных может повлиять совершенно не связанная
часть кода, или различие архитектур компьтеров, таким образом усложняя поиск источника ошибки.</p>
<p>Поэтому, ещё раз, используйте передачу адреса в качестве <code>KernelArg</code> только как тонкую оптимизацию и только когда понимаете что делаете.</p>
</div>
<script>on_start_folder("KernelArg", document.getElementById("page-10"))</script>
<script>on_start_folder("Неуправляемая память (Native-)", null)</script>
<div id="page-11" page_name="NativeValue" hidden=true>
<p>OpenCL это неуправляемая библиотека. Обычно можно заставить управляеммые типы данных работать с ней.<br />
Но часто это приводит к дополнительным затратам производительности.<br />
И обычно не значит всегда - <code>MemorySegment.ReadValue</code>, принимающее запись <code>var</code>-параметром не может быть безопасным из за сборщика мусора
(и поэтому отсутствует).</p>
<p>Более прямым будет передача неуправляемых типов - указателей - без преобразований в подпрограммы модуля <code>OpenCL</code>.<br />
И эта возможность тоже существует, к примеру в виде <code>MemorySegment.WriteData</code>. Но такие указатели ещё более не_безопасны:<br />
Как минимум они требуют освобождения в <code>try-finally</code> чтобы избежать утечек памяти.
И защиты от дурака, не возволяющей записать значение типа <code>real</code> туда, где хранится <code>int64</code> - не существует.</p>
<p>Как что-то среднее между этими двумя вариантами - существует <code>NativeValue<T></code>:<br />
Этот класс является обёрткой указателя на область памяти RAM.</p>
<pre><code>## uses OpenCLABC;
// В конструктор необходимо передавать значение,
// потому что иначе неуправляемая память будет содержать мусор
// Но можно передать default, чтобы заполнить выделяемую память нулями
var nv := new NativeValue<integer>(default(integer));
nv.Value := 5; // Перезаписываем значение,
nv.Value.Println; // И читаем назад
// Напрямую получать доступ к области памяти,
// через свойство Pointer, не рекомендуется
Writeln(nv.Pointer);
// Кроме как передавать nv.Pointer^ var-параметром
nv.Dispose; // Освобождение памяти - вызывается и само при сборке мусора
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-11"))</script>
<script>on_end_folder()</script>
<script>on_start_folder("Память OpenCL (CL-)", null)</script>
<div id="page-12" page_name="CLArray" hidden=true>
<p>Всё преимущество GPU над CPU состоит в выполнении одинаковой программы на рое из процессорных ядер.<br />
Каждое из этих ядер значительно медленнее ядер CPU, но используя весь рой вместе можно выполнять вычисления значительно быстрее.<br />
Но раз все ядра GPU выполняют одну и ту же программу - им обычно приходится передавать массив из однотипных данных.</p>
<p>Объект типа <code>CLArray<T></code> (где <code>T</code> - почти любая запись) является массивом, содержимое которого хранится в памяти GPU, для более быстрого доступа из кода на GPU.</p>
<hr />
<p><code>CLArray<T></code> создаётся конструктором:</p>
<pre><code>## uses OpenCLABC;
// Массив для 10 элементов типа integer
var a1 := new CLArray<integer>(Context.Default, 10);
// Заполняем нулями, потому что при создании CLArray может содержать мусорные данные
a1.FillValue(0);
a1.GetArray.Println;
// Массив, в который при создании копируются данные из переданного массива
var a2 := new CLArray<byte>(Context.Default, new byte[](1,2,3,4,5,6,7));
a2.GetArray.Println;
// Массив, в который при создании копируется часть данных из переданного массива
// Точнее, копируется 2 элемента, начиная с элемента с индексом 3
var cpu_array := |0,1,2,3,4,5,6,7|;
var a3 := new CLArray<integer>(Context.Default, 3, 2, cpu_array);
a3.GetArray.Println;
</code></pre>
<p>Кроме того, объект контекста можно не передавать, тогда для выделения памяти будет автоматически выбран <code>Context.Default</code>.</p>
<hr />
<p>Элементы этого массива, так же как у обычных массивов, можно читать через индексное свойство:</p>
<pre><code>## uses OpenCLABC;
var a := new CLArray<integer>(Context.Default, |0,1,4,9,16|);
a[3].Println;
a[4] := 5;
a[4].Println;
</code></pre>
<p>Но имейте в виду - каждое обращение к индексному свойству - это новое выполнение неявной очереди, поэтому заполнять или читать такой массив по 1 элементу не эффективно.</p>
<p>Лучше всего использовать полноценные очереди. Но если вы выполняете какое-то одноразовое заполнение - можно, хотя бы, использовать свойство <code>.Section</code>:</p>
<pre><code>## uses OpenCLABC;
var a := new CLArray<integer>(Context.Default, |0,1,4,9,16|);
// Заполняем элементы с индексами от 2 до 3 массивом |-1,-2|
a.Section[2..3] := |-1,-2|;
// Читаем и выводим элементы с 0 по 2 индексы как массив
a.Section[0..2].Println;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-12"))</script>
<div id="page-13" page_name="CLMemorySegment" hidden=true>
<p>Некоторые данные, передаваемые в программы на GPU, могут быть общими (для всех ядер) или просто не однотипными.<br />
Такие данные можно хранить в типе <code>MemorySegment</code>, представляющем область памяти GPU, для которой не определено ничего кроме размера.</p>
<p>Область памяти <code>MemorySegment</code> создаётся конструктором (<code>new MemorySegment(...)</code>).<br />
Основные конструкторы принимают размер области памяти и, опционально, контекст. Если контекст не указывается - память выделяется на <code>Context.Default</code>.</p>
<p>Далее, в неё можно записывать и читать данные практически любого типа записи, в любой точке:</p>
<pre><code>## uses OpenCLABC;
var s := new MemorySegment(sizeof(int64));
// $ Это запись числа в 16-ичной СС
s.WriteValue&<int64>($FEDCBA9876543210);
// .ToString('X') преобразовывает число в строку в 16-ичной СС
s.GetValue&<int64>.ToString('X').Println;
// Можно читать кусок данных одного типа, как данные другого типа
s.GetValue&<integer>.ToString('X').Println;
// Читаем начиная с четвёртого байта
// Я сделал 16-ичный вывод чтобы было видно, изначальное число разрезало пополам
s.GetValue&<integer>(4).ToString('X').Println;
// Ну и никто не запретит прочитать как совершенно не совместимый тип данных
// Но в ответ вы получите мусорные данные
s.GetValue&<real>.Println;
</code></pre>
<p>То есть, вам самим придётся заботится о типе внутренних данных, работая с <code>MemorySegment</code>, но взамен вы полностью контролируете содержимое области памяти.</p>
<hr />
<p>Память на GPU можно моментально освободить, вызвав метод <code>MemorySegment.Dispose</code>.<br />
Но вызывать его не обязательно, потому что этот метод вызывается автоматически во время сборки мусора.</p>
</div>
<script>on_page_added(document.getElementById("page-13"))</script>
<div id="page-14" page_name="CLMemorySubSegment" hidden=true>
<p>Но кроме выделения памяти на GPU - OpenCL так же позволяет выделять память внутри другой области памяти.<br />
Для этого используется тип <code>MemorySubSegment</code>:</p>
<pre><code>## uses OpenCLABC;
var c := Context.Default;
// Не обязательно MainDevice, можно взять любое устройство из контекста
var align := c.MainDevice.Properties.MemBaseAddrAlign;
var s := new MemorySegment(align*2, c);
// size может быть любым, но origin
// должно быть align*N, где N - целое
var s1 := new MemorySubSegment(s, 0, Min(123,align));
var s2 := new MemorySubSegment(s, align, align);
Writeln(s1);
Writeln(s2);
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-14"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
<div id="page-15" page_name="" hidden=true>
<p>Передавать команды для GPU по одной не эффективно.
Гораздо эффективнее передавать несколько команд сразу.</p>
<p>Для этого существуют очереди (типы, наследующие от <code>CommandQueueBase</code>).
Они хранят произвольное количество команд для GPU.
А при необходимости также и части кода, выполняемые на CPU.</p>
<hr />
<h1>Страницы:</h1>
<ul>
<li><p><a path="Возвращаемое значение очередей/"> Возвращаемое значение </a></p>
</li>
<li><p><a path="Выполнение очередей/"> Выполнение </a></p>
</li>
<li><p><a path="Создание очередей/"> Создание </a></p>
</li>
<li><p><a path="Вложенные очереди/"> Вложенные очереди </a></p>
</li>
</ul>
</div>
<script>on_start_folder("Очередь [команд] (CommandQueue)", document.getElementById("page-15"))</script>
<div id="page-16" page_name="" hidden=true>
<p>Все типы очередей наследует от <code>CommandQueueBase</code>. Это значит что любую очередь можно сохранить в переменную типа <code>CommandQueueBase</code>.<br />
Но о значении типа <code>CommandQueueBase</code> известно не на много больше чем о значении типа <code>object</code>.</p>
<p>Так же, все очереди наследуют от одного из двух типов:</p>
<ol>
<li><code>CommandQueueNil</code> - очередь возващающая <code>nil</code> (именно нулевую ссылку, не пустое значение любого типа).</li>
<li><code>CommandQueue<T></code> (где <code>T</code> - любой тип) - очередь возвращающая значение типа <code>T</code>;</li>
</ol>
<p>После выполнения очереди <code>CommandQueue<T></code> метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br />
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask<T>.WaitRes</code>.</p>
<hr />
<p>Результат других типов очередей нельзя получить, но их можно преобразовать к <code>CommandQueue<T></code> с произвольным <code>T</code> с помощью <code>.Cast</code>:</p>
<pre><code>## uses OpenCLABC;
// Q объявлена как CommandQueueBase,
// а значит в неё можно сохранить любую очередь
var Q: CommandQueueBase;
// В данном случае сохраняем CommandQueueNil
Q := HPQ(()->Writeln('Q выполнилась'));
// Преобразовывать nil можно в любой ссылочный тип
Writeln(Context.Default.SyncInvoke( Q.Cast&<object> ));
// Exception тоже класс - поэтому можно и в него
// Но в результате всё равно получится nil
Writeln(Context.Default.SyncInvoke( Q.Cast&<Exception> ));
Sleep(1000); // Чтобы было видно предыдущий вывод
//Ошибка времени выполнения: .Cast не может преобразовывать nil в integer
// Ошибка кидается ещё в момент создания .Cast очереди
Writeln(Context.Default.SyncInvoke( Q.Cast&<integer> ));
</code></pre>
<p>Подробнее <a path="../Создание очередей/С кодом для CPU">тут</a>.</p>
<hr />
<p>В то же время, если результат выполнения очереди не нужен,
от него можно избавится с помощью <code>.DiscardResult</code>.</p>
<p>Это может понадобится если вы хотите:</p>
<ul>
<li>Защититься от случайного использования результата;</li>
<li>Передать очередь туда, где ожидается <code>CommandQueueNil</code>;</li>
<li>Сократить (мизерное, но не нулевое) использование ресурсов, выделяемых для передачи резутата. К примеру:</li>
</ul>
<pre><code>## uses OpenCLABC;
var Q := HFQQ(()->
begin
Writeln('Функция выполнилась');
Result := 5;
end).DiscardResult;
var t := Context.Default.BeginInvoke(Q as CommandQueueBase);
$'Has result: {not (t is CLTaskNil)}'.Println;
t.Wait;
</code></pre>
<p>Без <code>.DiscardResult</code> - <code>BeginInvoke</code> создаст <code>CLTask<integer></code>,
не смотря на то, что в него передают <code>CommandQueueBase</code>.</p>
<p>А с <code>.DiscardResult</code> - <code>HFQQ</code> выполняется как будто это <code>HPQQ</code>.</p>
<p>Обратите внимание, если результат очереди выкидывается, без
какого-либо использования - ресурсы на него не выделяются. К примеру:</p>
<pre><code>## uses OpenCLABC;
Context.Default.SyncInvoke(
HFQQ(()->1) +
HFQQ(()->2)
);
</code></pre>
<p>Первая <code>HFQQ</code> выполнится как будто это <code>HPQQ</code>, потому что
из <code>SyncInvoke</code> можно получить только результат второй <code>HFQQ</code>.</p>
<hr />
<p>Очереди, созданные из областей памяти OpenCL или kernel'ов возващают свои области памяти/<code>Kernel</code>'ы соответственно, из которых были созданы;<br />
Очереди, созданные с <code>HF[Q]Q</code> - значение, которое вернёт переданная функция;<br />
Очереди, созданные с <code>HP[Q]Q</code> являются <code>CommandQueueNil</code>.</p>
<p>Демонстрация:</p>
<pre><code>## uses OpenCLABC;
/// Вывод типа и значения объекта
procedure OtpObject(o: object) :=
$'{if o=nil then nil else TypeName(o)}[{_ObjectToString(o)}]'.Println;
// _ObjectToString это функция, которую используют
// Writeln и Println для форматирования значений
procedure Test(q: CommandQueueBase) :=
OtpObject(Context.Default.SyncInvoke(
// Преобразовываем результат к object, чтобы его вернула SyncInvoke
q.Cast&<object>
));
var s := new MemorySegment(1);
// Тип - MemorySegment, потому что очередь создали из него
Test( s.NewQueue );
// Тип - integer, потому что это тип по умолчанию для выражения (5)
Test( HFQQ(()->5) );
// Тип - string, по той же причине
Test( HFQQ(()->'abc') );
// Тип отсутствует, потому что HP[Q]Q возвращает nil
Test( HPQQ(()->Print('Выполнилась HPQQ:')) );
</code></pre>
<hr />
<p>Проверить что очередь ничего не возвращает очень просто:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
p1(cqn) else
p2(Q);
</code></pre>
<p>Но для типа <code>CommandQueue<T></code> надо указать конкретный тип, чтобы вызвать <code>is</code>.
Другими словами, с помощью <code>is</code> можно проверять только по одному типу возвращаемого значения за раз:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
p1(cqn) else
if Q is CommandQueue<byte>(var cq) then
p2&<byte>(cq) else
if Q is CommandQueue<word>(var cq) then
p2&<word>(cq) else
// Не должно происходить
raise new System.NotSupportedException;
</code></pre>
<p>Если надо вызвать <code>p2</code> для очереди с любым возвращаемым значением - используется <code>.UseTyped</code>:</p>
<pre><code>uses OpenCLABC;
procedure p1(cq: CommandQueueNil) := Writeln('nil');
procedure p2<T>(cq: CommandQueue<T>) := Writeln($'<{TypeToTypeName(typeof(T))}>');
type
// Не обязательно запись
TypedUser = record(ITypedCQUser)
public procedure UseNil(cq: CommandQueueNil) := p1(cq);
public procedure Use<T>(cq: CommandQueue<T>) := p2(cq);
end;
procedure Test(Q: CommandQueueBase) :=
Q.UseTyped(new TypedUser);
begin
Test(HPQ(()->begin end));
Test(HFQ(()->0));
Test(HFQ(()->0.0));
end.
</code></pre>
<p>Объявлять дополнительный тип (<code>TypedUser</code> в этом коде) необходимо потому, что иначе
передать подпрограмму <code>Use<T></code>, не указывая её <code><T></code>, в <code>UseTyped</code> не получится.</p>
<p>Так же, если нужно не только использовать очередь, но и что-то вернуть - используется <code>.ConvertTyped</code>:</p>
<pre><code>uses OpenCLABC;
type
// Получает имя типа результата очереди, или nil если он отсутствует
QueueConverterResTName = record(ITypedCQConverter<string>)
public function ConvertNil(cq: CommandQueueNil): string := nil;
public function Convert<T>(cq: CommandQueue<T>): string := TypeToTypeName(typeof(T));
end;
procedure Test(Q: CommandQueueBase) :=
Writeln( Q.ConvertTyped(new QueueConverterResTName) );
begin
Test(HPQ(()->begin end));
Test(HFQ(()->0));
Test(HFQ(()->0.0));
end.
</code></pre>
<p>И <code>.UseTyped</code> и <code>.ConvertTyped</code> гарантируют что обязательно будет вызван ровно один
из двух методов - либо принимающий <code>CommandQueueNil</code>, либо принимающий <code>CommandQueue<T></code>.</p>
</div>
<script>on_start_folder("Возвращаемое значение очередей", document.getElementById("page-16"))</script>
<script>on_end_folder()</script>
<div id="page-17" page_name="" hidden=true>
<p>Самый простой способ выполнить очередь - вызвать метод <code>Context.SyncInvoke</code>.</p>
<p>У него есть три перегрузки, для <code>CommandQueueBase</code>, <code>CommandQueueNil</code> и <code>CommandQueue<T></code>.
Только последняя возвращает результат.</p>
<p>Но если надо выполнить очередь асинхронно - лучше использовать метод <code>Context.BeginInvoke</code>,
потому что его всё равно вызывает <code>Context.SyncInvoke</code>.</p>
<p><code>Context.BeginInvoke</code> запускает асинхронное выполнение очереди.
Как только очередь была полностью запущена он возвращает объект типа
<code>CLTaskBase</code>, <code>CLTaskNil</code> или <code>CLTask<T></code> для соответствующих типов очередей.</p>
<p>Так же как в случае очередей, <code>CLTaskNil</code> и <code>CLTask<T></code> наследуют от <code>CLTaskBase</code>.<br />
У всех <code>CLTask</code>-ов есть:</p>
<ul>
<li>Свойства <code>.OrgContext</code> и <code>.OrgQueue</code>, возвращающие контекст выполнения и выполняемую очередь соответственно.</li>
<li>Метод <code>.Wait</code> для ожидания окончания выполнения очереди.</li>
</ul>
<p>У <code>CLTask<T></code> так же есть метод <code>.WaitRes</code>, вызывающий <code>.Wait</code> и затем возвращающий результат очереди.</p>
<hr />
<p>При выполнении очереди может произойти несколько исключений, поэтому, чтобы получить можно было все, их упаковывает в <code>System.AggregateException</code>:</p>
<pre><code>## uses OpenCLABC;
try
// Context.SyncInvoke или CLTask.Wait
Context.Default.SyncInvoke(Q);
except
on e: System.AggregateException do
e.InnerExceptions.PrintLines;
end;
</code></pre>
<p>Но для более тонкого контроля лучше использовать
<a path="../Создание очередей/Обработка исключений"><code>.Handle*</code> методы очередей</a>.</p>
</div>
<script>on_start_folder("Выполнение очередей", document.getElementById("page-17"))</script>
<script>on_end_folder()</script>
<div id="page-18" page_name="" hidden=true>
<h1>Есть всего 10 базовых способов создать очередь:</h1>
<ol>
<li><p><a path="Из объекта с коммандами"> Из объекта с коммандами </a></p>
</li>
<li><p><a path="Из готового результата"> Из готового результата </a></p>
</li>
<li><p><a path="С кодом для CPU"> Из обычной подпрограммы </a></p>
</li>
<li><p><a path="Комбинируя другие очереди"> Из нескольких других очередей </a></p>
</li>
<li><p><a path="Из повторения очереди"> Из повторения одной очереди </a></p>
</li>
<li><p><a path="Множественное использование очереди"> Несколько очередей с общим результатом </a></p>
</li>
<li><p><a path="Общие методы CCQ объектов"> Общими методами CCQ объектов </a></p>
</li>
<li><p><a path="Из ожидания очередей"> Из ожидания других очередей </a></p>
</li>
<li><p><a path="Обработка исключений"> Из очереди + обработчика исключений </a></p>
</li>
<li><p><a path="Не создавая явно"> Не пытаясь </a></p>
</li>
</ol>
</div>
<script>on_start_folder("Создание очередей", document.getElementById("page-18"))</script>
<div id="page-19" page_name="Из объекта с коммандами" hidden=true>
<p>OpenCL используется для передачи комманд в GPU.
Поэтому в первую очередь стоит поговорить об очередях <code>OpenCLABC</code>, содержащих комманды для GPU.</p>
<p>Такие очереди создаются из некоторых типов-простых_обёрток методом <code>.NewQueue</code> и имеют тип с припиской <code>CCQ</code>.<br />
("Command Container Queue", то есть очередь-контейнер для коман GPU)<br />
К примеру, метод <code>CLArray<byte>.NewQueue</code> вернёт очередь типа <code>CLArrayCCQ<byte></code>, наследующего от <code>CommandQueue< CLArray<byte> ></code>.</p>
<p>Чтобы создать саму комманду - надо вызвать соответствующий ей метод <code>CCQ</code> объекта. К примеру:</p>
<pre><code>## uses OpenCLABC;
// Массив на 3 элемента типа integer
var a := new CLArray<integer>(3);
// Создаём очередь
var q: CLArrayCCQ<integer> := a.NewQueue;
Context.Default.SyncInvoke(q
.ThenWriteValue(1, 0)
// Записывать по 1 элементу не эффективно
// Лучше сначала создать массив в RAM
// А затем послать его целиком
.ThenWriteArray(|5,7|, 1, 2,0)
// .ThenGet методы особенные, потому что не возвращают CCQ объект
// В данном случае эта комманда читает весь CLArray как обычный массив в RAM
.ThenGetArray
).Println;
</code></pre>
<p>Также, <code>CCQ</code> очереди можно создавать из очередей, возвращающих объект с командами. Для этого используется конструктор:</p>
<pre><code>var q0: CommandQueue<MemorySegment>;
...
var q := new MemorySegmentCCQ(q0);
</code></pre>
<hr />
<p>Команды объектов, представляющих память на GPU, можно разделить на группы.</p>
<p>По направлению передачи:</p>
<ol>
<li><code>Write</code> и <code>Fill</code>: Из RAM в память GPU;</li>
<li><code>Read</code> и <code>Get</code>: Из памяти GPU в RAM;</li>
<li><code>Copy</code>: Между двумя областями памяти GPU.</li>
</ol>
<p>И по типу данных на стороне RAM:</p>
<ol>
<li><code>Data</code>: Используются данные, находящиеся в RAM по указанному адресу;</li>
<li><code>Value</code>: Используется размерное значение;</li>
<li><code>Array</code>: Используется содержимое указанного массива размерных значений.</li>
</ol>
<p>Но при этом отсутствуют некоторые комбинации.</p>
<p>В первую очередь, в случае <code>Copy</code> нет понятия типа данных на стороне RAM, потому что RAM в принципе не задействуется.</p>
<p>И нет <code>GetData</code>, потому что в случае ошибок будут утечки памяти.
Вместо него выделяйте память явно и передавайте её в <code>ReadData</code>.</p>
<p>Так же, <code>WriteValue</code> может принимать размерное значение и <code>NativeValue</code>, но <code>ReadValue</code> принимает только <code>NativeValue</code>.<br />
Это потому, что принимать размерное значение в <code>ReadValue</code> <code>var</code>-параметром не безопасно,
как и в случае передачи адреса в качестве <a path="../../Простые обёртки/Kernel/KernelArg"> <code>KernelArg</code> </a>.<br />
Если вы понимаете что делаете - используйте <code>ReadData</code>, явно передавая в него адрес вашего значения (то есть указатель).<br />
Но обычно лучше использовать <code>GetValue</code>, создающее и возвращающее новое размерное значение, либо <code>ReadValue</code> принимающее <code>NativeValue</code>.</p>
<hr />
<p>Кроме таких объектов, методы-команды для GPU есть только у <code>Kernel</code>. И все они представляют запуск kernel'а.</p>
<hr />
<p>Комманда <code>.ThenGet</code> не выполняется, если её результат выкидывается.
Подробнее <a path="../Возвращаемое значение очередей/">тут</a>.</p>
<p>Это оптимизация, которую вы можете заметить только в одном случае:
Если параметры этой команды были неправильные - ошибка не возникнет.</p>
</div>
<script>on_page_added(document.getElementById("page-19"))</script>
<div id="page-20" page_name="Из готового результата" hidden=true>
<p>Константы в любом контексте позволяют проводить особые оптимизации.<br />
В случае <code>OpenCLABC</code> - константные очереди являются константами только на время выполнения очереди.</p>
<pre><code>## uses OpenCLABC;
var cq := new ConstQueue<integer>(1);
Context.Default.SyncInvoke(cq).Println;
cq.Value := 2;
Context.Default.SyncInvoke(cq).Println;
</code></pre>
<p>Обратите внимание, <code>.Value</code> нельзя изменять пока <code>.BeginInvoke</code> не досоздаст <code>CLTask</code>. Иначе поведение неопределено.</p>
<hr />
<p>Так же, константную очередь можно создать присвоив значение результата туда, где ожидается очередь:</p>
<pre><code>var q: CommandQueue<integer> := 1;
</code></pre>
<p>Для этого тип значения (<code>1</code>) и результата очереди <code><integer></code> должны полностью совпадать. К примеру так сделать не позволит:</p>
<pre><code>//Ошибка: Нельзя преобразовать тип integer к CommandQueue<object>
var q: CommandQueue<object> := 1;
</code></pre>
<p>Чтобы можно было присвоить - значение надо явно преобразовать к <code>object</code>:</p>
<pre><code>var q: CommandQueue<object> := object(1);
</code></pre>
<p>Этот способ создания константной очереди обычно используется подпрограммами, принимающими очереди:</p>
<pre><code>## uses OpenCLABC;
procedure p1(q: CommandQueue<integer>) :=
Context.Default.SyncInvoke(q).Println;
p1(1);
p1(HFQQ(()->2));
</code></pre>
<p>В <code>OpenCLABC</code> есть множество подпрограмм, принимающий очередь вместо значения.
<a path="../Вложенные очереди/">Подробнее</a>.</p>
<hr />
<p>Если нужно сохранить константную очередь в переменную, можно написать:</p>
<pre><code>var q1 := new ConstQueue<integer>(1);
var q2: CommandQueue<integer> := 3;
</code></pre>
<p>Но оба способа объёмны и, что важнее, требуют указать тип значения явно.</p>
<p>Поэтому в <code>OpenCLABC</code> есть вспомогательная подпрограмма:</p>
<pre><code>var q3 := CQ(3);
</code></pre>
<hr />
<p>У очередей есть возвращаемое значение, но чтобы передавать в них данные при запуске - можно
или создавать новую очередь на каждое выполнение (что очень не эффективно), или выкручиваться так:</p>
<pre><code>uses OpenCLABC;
type
MyQueueContainer = sealed class
private Q: CommandQueue<integer>;
private par1 := new ConstQueue<integer>(-1);
private par2 := new ConstQueue<string>(nil);
// Эта очередь ничего полезного не делает, но это только пример
public constructor := self.Q :=
MemorySegment.Create(sizeof(integer)).NewQueue
.ThenWriteValue( self.par1 )
.ThenQueue( self.par2.ThenQuickUse(x->Writeln(x)) )
.ThenGetValue&<integer>;
public function Invoke(par1: integer; par2: string): integer;
begin
var tsk: CLTask<integer>;
// Нужна блокировка, чтобы если метод Invoke будет выполнен
// в нескольких потоках одновременно, .Value параметров
// не могло поменяться пока Context.BeginInvoke создаёт CLTask
lock self do
begin
self.par1.Value := par1;
self.par2.Value := par2;
tsk := Context.Default.BeginInvoke(Q);
end;
Result := tsk.WaitRes;
end;
end;
begin
var cont := new MyQueueContainer;
cont.Invoke(1, 'abc').Println;
cont.Invoke(2, 'def').Println;
end.
</code></pre>
<p>Но это не очень красиво. Можно сделать красивее, используя специальную очередь-параметр.<br />
<code>ParameterQueue<T></code> тоже является константной во время выполнения очереди, но используется по-другому:</p>
<pre><code>uses OpenCLABC;
type
MyQueueContainer = sealed class
private Q: CommandQueue<integer>;
private par1 := new ParameterQueue<integer>('par1');
private par2 := new ParameterQueue<string>('par2');
// Эта очередь ничего полезного не делает, но это только пример
public constructor := self.Q :=
MemorySegment.Create(sizeof(integer)).NewQueue
.ThenWriteValue( self.par1 )
.ThenQueue( self.par2.ThenQuickUse(x->Writeln(x)) )
.ThenGetValue&<integer>;
public function Invoke(par1: integer; par2: string) :=
Context.Default.SyncInvoke(self.Q,
self.par1.NewSetter(par1),
self.par2.NewSetter(par2)
);
end;
begin
var cont := new MyQueueContainer;
cont.Invoke(1, 'abc').Println;
cont.Invoke(2, 'def').Println;
end.
</code></pre>
<p>Таким образом код получается значительно чище.<br />
Кроме того, при запуске очереди проводятся проверки, чтобы очереди-параметры
не оказывались не_определены или установлены дважды.</p>
</div>
<script>on_page_added(document.getElementById("page-20"))</script>
<div id="page-21" page_name="С кодом для CPU" hidden=true>
<p>Иногда между командами для GPU надо вставить выполнение обычного кода на CPU.
А разрывать для этого очередь на две части - плохо, потому что
одна целая очередь всегда выполнится быстрее двух её частей.</p>
<p>Поэтому существует множество типов очередей, хранящих обычный код для CPU.</p>
<hr />
<p>Чтобы создать самую простую такую очередь используются глобальные подпрограммы <code>HFQ</code> и <code>HPQ</code>:</p>
<p>HFQ — Host Function Queue<br />
HPQ — Host Procedure Queue<br />
(Хост в контексте OpenCL - это CPU, потому что с него посылаются команды для GPU)</p>
<p>Они возвращают очередь, выполняющую код (функцию/процедуру соотвественно) на CPU.<br />
Пример применения приведён <a path="../Возвращаемое значение очередей/"> на странице выше</a>.</p>
<hr />
<p>Так же бывает нужно использовать результат предыдущей очереди в коде на CPU.
Для этого используются методы <code>.ThenUse</code> и <code>.ThenConvert</code>:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQ(()->5);
Context.Default.SyncInvoke(Q
.ThenUse(x->Println($'x*2 = {x*2}'))
.ThenConvert(x->$'x^2 = {x**2}')
).Println;
</code></pre>
<p><code>.ThenUse</code> дублирует возвращаемое значение предыдущей очереди (<code>Q</code> в примере).<br />
А <code>.ThenConvert</code> возвращает результат выполнения переданной функции, как и <code>HFQ</code>.</p>
<hr />
<p><code>OpenCLABC</code> очереди существуют чтобы можно было удобно описывать параллельные процедуры.
Поэтому, с расчётом на параллельность, обычные очереди с кодом для CPU создают себе по одному потоку выполнения (<code>Thread</code>) при запуске.</p>
<p>Этот поток выполнения запускается до выхода из <code>Context.BeginInvoke</code> и остаётся в режиме ожидая.<br />
Но даже если игнорировать затраты на запуск потока, выход из режима ожидания это не моментальня операция.</p>
<p>Если надо выполнить очень простое действие, как в последнем примере выше, эти затраты неоправданны.<br />
Для таких случаев используется <code>Quick</code> версии очередей:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQQ(()->5);
Context.Default.SyncInvoke(Q
.ThenQuickUse(x->Println($'x*2 = {x*2}'))
.ThenQuickConvert(x->$'x^2 = {x**2}')
).Println;
</code></pre>
<p>В отличии от предыдущего примера, в данном будет создан только один поток выполнения (его всегда создаёт <code>Context.BeginInvoke</code>).</p>