Итак, думаю есть смысл обсудить, что и где лучше применять, а точнее ссылочные или размерные типы. Мнения в этом вопросе расходятся: например, @ibond считает, что размерные типы негативно влияют на производительность, а вот авторы нескольких статей на Хабре(но уж точно не наш Илья ) совершенно противоположного мнения. Причём каждая позиция имеет под собой весьма серьёзные основания. Хотел бы выслушать мнения.
Глеб, лень искать все это у ibonda и на Хабре… Если уж вы подробно ознакомились с их позициями, то попробуйте здесь кратко изложить все их аргументы и в каком контексте они были сказаны. Ну, или хотя бы точные ссылки на сами посты и статьи. А то пока и обсуждать-то нечего
Но предполагаю, что они все правы по-своему, только каждый – применительно к своей конкретной ситуации.
Хорошая идея.
Итак, преимущества ссылочных типов:
-
Возможность выделить достаточно большое количество памяти и высвободить её в любой момент.
-
Гибкость динамических объектов.
Недостатки:
-
Объекты ссылочного типа располагаются в куче, а эта область памяти подчиняется сборщику мусора, службе, которая может периодически перемещать объект, на это тратятся ресурсы.
-
Ссылочные объекты имеют достаточно сложную структуру, что опять таки негативно сказывается на производительности.
-
Выделение памяти в куче происходит ощутимо дольше, чем в стеке.
Размерные типы:
Преимущества:
-
Моментальное выделение памяти в стеке.
-
Отсутствие затрат на перемещение объекта при сборке мусора.
Недостатки:
-
Невысокая гибкость. Невозможность управляемого высвобождения памяти.
-
Ограничения на размер.
Размерные типы размещаются в памяти ближе к процессору, поэтому в целом они быстрее. Но если, к примеру, передавать их в функцию - будет копироваться вся переменная, и если они весит много а копируется часто - это будет негативно влиять на производительность. Но, кстати, если передавать их как var
-параметры - в функцию будет копироваться только указатель на них. Но доступ к var
-параметрам немного медленнее, потому что надо разыменовывать указатель каждый раз.
Ну, это конечно можно к ним отнести, но не в том дело. В принципе, памяти в куче больше. В теории, можно наверное как то через WinAPI выделять память в куче, а ссылочность тут не при чём.
Вы про что?
Оно вроде только если места не хватает перемещает всё, да и ресурсов для этого много не надо.
Ну, не совсем. Единственное это то что каждая переменная ссылочного типа - это указатель, получение значения по этому указателю - наверное единственное что влияет на производительность.
Выделение, вроде, нет, только доступ к памяти, и только потому что куча располагается дальше от процессора чем стек.
Сомневаюсь, что вы сможете без рекурсии добится заполнения стека. А так его объём можно приравнивать к бесконечности.
Стоп. Это замедление ведь очень мало.
Что ещё хуже.
Да, но речь идёт о непрерывном объекте. Заполнить его кусками информации сложно, но вот одним - легко. Мне где-то попадались цифра примерно 75 мБ под объект.
Ну наверное жешь. Но вы ведь хотите полную картину, вот я для полноты написал.
Ну это массив же, или какой то объект с внутренними массивами? А в паскале нет массивов размещающихся в стеке.
Скажу тогда, зачем всё это вообще нужно мне. Я реализуются нейросеть на Паскале, да такую, которую обычно на серверных GPU запускают. Очевидно, что надо оптимизировать по максимуму. Смысл в том, что нейросеть содержит веса, на которые определённым образом умножаются данные. Например, четырёхмерном массив Single с размерами [3, 3, 512, 512] (остроконечный формат с лева на право). Так вот, эти веса инициализируются один раз в начале программы и никак не меняются. Может, есть смысл их засунуть в стек?
Да, наверное, но в паскале вы это никак (кроме как вручную, по одному элементу объявлять) не сделаете.
Надо делать на C++, и потом делать обёртку в паскале. Через external
подключить функцию создающую массив длинной 33512*512 и возвращающую ссылку на первый элемент.
Хотя, это похоже займёт в памяти >9MB, а стек обычно в районе 1MB. По крайней мере на сколько я понял покопавшись в интернете. Как точно узнать объём - нашёл несколько очень низкоуровневых решений на C++ и следующие решение, которое работает только на вин 8 и 10:
procedure GetCurrentThreadStackLimits(lowLimit: ^cardinal; highLimit: ^cardinal);
external 'kernel32.dll';
begin
var l,h:cardinal;
GetCurrentThreadStackLimits(@l,@h);
writeln((l,h));
end.
У меня вин7, поэтому проверить работоспособность не смог.
Вообще, ускорения надо добиваться настраивая горы GPU-шек, а махинациями с тем, где всё размещается, вы скорее всего не больше 1% разницы сделаете.
Ещё OpenCL можно использовать, если GPU нет подходящих.
Нет, ну зачем извращаться, System.Threading.Tasks.Parallel
сделаете всё то что может OpenCL, разве нет?
Не знаю, просто видел примеры проектов с использованием OpenCL в том числе и для нейронных сетей. Поставил один такой у себя (не нейросеть), он показывает что использует обычный встроенный графический процессор в Intel.
А можно ли как-то управлять GPU из под .NET? Чтобы не плодить кучу файлов? Если обычный GPU будет работать, то я, возможно, задействуют его.
Только имейте ввиду, что сейчас процесс занимает более 2-х часов(не просто так я сказал про сервер). Посчитайте 1% от 3 часов. Но зато это первая реализация под Windows, причём написана она не на хвалёном C++ и Python, а на C# и PascalABC.NET
Нет уж, лучше вручную на Паскале, чем на C++. Паскаль, по моему, удобнее.
Самому интересно. Тот проект с OpenCL о котором писал уже это Ready. С таким названием сложно искать, вот ссылка https://github.com/GollyGang/ready Но он на С++. Просто можете взять в release последнюю версию и посмотреть, есть ли смысл вообще связываться с OpenCL на Вашем железе. А о применении к нейронным сетям на NET, OpenCL уже можете потом сами поискать, там вроде даже какие-то статьи на Хабр выскакивали.
Вроде у OpenCL есть обёртка под .Net, что то типа OpenTK
Вы наверное меня не поняли, вам придётся объявить 2 записи с 512 элементами. И это в лучшем случае. Потому что в паскале не выйдет сделать массив который суётся в стек.
Но всё же, как я и сказал, вряд ли всё это поместится в стек. И разница в скорости доступа будет небольшая всё равно.
Меньше 2 мин, если от 3 часов брать. Но, кроме всего, надо сначала провести тесты скорости, 1% это я отфига взял.
Вот к примеру:
type
r1 = record
value1: single;
value2: single;
value3: single;
value4: single;
value5: single;
value6: single;
value7: single;
value8: single;
value9: single;
value10: single;
value11: single;
value12: single;
value13: single;
value14: single;
value15: single;
value16: single;
value17: single;
value18: single;
value19: single;
value20: single;
value21: single;
value22: single;
value23: single;
value24: single;
value25: single;
value26: single;
value27: single;
value28: single;
value29: single;
value30: single;
value31: single;
value32: single;
value33: single;
value34: single;
value35: single;
value36: single;
value37: single;
value38: single;
value39: single;
value40: single;
value41: single;
value42: single;
value43: single;
value44: single;
value45: single;
value46: single;
value47: single;
value48: single;
value49: single;
value50: single;
value51: single;
value52: single;
value53: single;
value54: single;
value55: single;
value56: single;
value57: single;
value58: single;
value59: single;
value60: single;
value61: single;
value62: single;
value63: single;
value64: single;
value65: single;
value66: single;
value67: single;
value68: single;
value69: single;
value70: single;
value71: single;
value72: single;
value73: single;
value74: single;
value75: single;
value76: single;
value77: single;
value78: single;
value79: single;
value80: single;
value81: single;
value82: single;
value83: single;
value84: single;
value85: single;
value86: single;
value87: single;
value88: single;
value89: single;
value90: single;
value91: single;
value92: single;
value93: single;
value94: single;
value95: single;
value96: single;
value97: single;
value98: single;
value99: single;
value100: single;
value101: single;
value102: single;
value103: single;
value104: single;
value105: single;
value106: single;
value107: single;
value108: single;
value109: single;
value110: single;
value111: single;
value112: single;
value113: single;
value114: single;
value115: single;
value116: single;
value117: single;
value118: single;
value119: single;
value120: single;
value121: single;
value122: single;
value123: single;
value124: single;
value125: single;
value126: single;
value127: single;
value128: single;
value129: single;
value130: single;
value131: single;
value132: single;
value133: single;
value134: single;
value135: single;
value136: single;
value137: single;
value138: single;
value139: single;
value140: single;
value141: single;
value142: single;
value143: single;
value144: single;
value145: single;
value146: single;
value147: single;
value148: single;
value149: single;
value150: single;
value151: single;
value152: single;
value153: single;
value154: single;
value155: single;
value156: single;
value157: single;
value158: single;
value159: single;
value160: single;
value161: single;
value162: single;
value163: single;
value164: single;
value165: single;
value166: single;
value167: single;
value168: single;
value169: single;
value170: single;
value171: single;
value172: single;
value173: single;
value174: single;
value175: single;
value176: single;
value177: single;
value178: single;
value179: single;
value180: single;
value181: single;
value182: single;
value183: single;
value184: single;
value185: single;
value186: single;
value187: single;
value188: single;
value189: single;
value190: single;
value191: single;
value192: single;
value193: single;
value194: single;
value195: single;
value196: single;
value197: single;
value198: single;
value199: single;
value200: single;
value201: single;
value202: single;
value203: single;
value204: single;
value205: single;
value206: single;
value207: single;
value208: single;
value209: single;
value210: single;
value211: single;
value212: single;
value213: single;
value214: single;
value215: single;
value216: single;
value217: single;
value218: single;
value219: single;
value220: single;
value221: single;
value222: single;
value223: single;
value224: single;
value225: single;
value226: single;
value227: single;
value228: single;
value229: single;
value230: single;
value231: single;
value232: single;
value233: single;
value234: single;
value235: single;
value236: single;
value237: single;
value238: single;
value239: single;
value240: single;
value241: single;
value242: single;
value243: single;
value244: single;
value245: single;
value246: single;
value247: single;
value248: single;
value249: single;
value250: single;
value251: single;
value252: single;
value253: single;
value254: single;
value255: single;
value256: single;
value257: single;
value258: single;
value259: single;
value260: single;
value261: single;
value262: single;
value263: single;
value264: single;
value265: single;
value266: single;
value267: single;
value268: single;
value269: single;
value270: single;
value271: single;
value272: single;
value273: single;
value274: single;
value275: single;
value276: single;
value277: single;
value278: single;
value279: single;
value280: single;
value281: single;
value282: single;
value283: single;
value284: single;
value285: single;
value286: single;
value287: single;
value288: single;
value289: single;
value290: single;
value291: single;
value292: single;
value293: single;
value294: single;
value295: single;
value296: single;
value297: single;
value298: single;
value299: single;
value300: single;
value301: single;
value302: single;
value303: single;
value304: single;
value305: single;
value306: single;
value307: single;
value308: single;
value309: single;
value310: single;
value311: single;
value312: single;
value313: single;
value314: single;
value315: single;
value316: single;
value317: single;
value318: single;
value319: single;
value320: single;
value321: single;
value322: single;
value323: single;
value324: single;
value325: single;
value326: single;
value327: single;
value328: single;
value329: single;
value330: single;
value331: single;
value332: single;
value333: single;
value334: single;
value335: single;
value336: single;
value337: single;
value338: single;
value339: single;
value340: single;
value341: single;
value342: single;
value343: single;
value344: single;
value345: single;
value346: single;
value347: single;
value348: single;
value349: single;
value350: single;
value351: single;
value352: single;
value353: single;
value354: single;
value355: single;
value356: single;
value357: single;
value358: single;
value359: single;
value360: single;
value361: single;
value362: single;
value363: single;
value364: single;
value365: single;
value366: single;
value367: single;
value368: single;
value369: single;
value370: single;
value371: single;
value372: single;
value373: single;
value374: single;
value375: single;
value376: single;
value377: single;
value378: single;
value379: single;
value380: single;
value381: single;
value382: single;
value383: single;
value384: single;
value385: single;
value386: single;
value387: single;
value388: single;
value389: single;
value390: single;
value391: single;
value392: single;
value393: single;
value394: single;
value395: single;
value396: single;
value397: single;
value398: single;
value399: single;
value400: single;
value401: single;
value402: single;
value403: single;
value404: single;
value405: single;
value406: single;
value407: single;
value408: single;
value409: single;
value410: single;
value411: single;
value412: single;
value413: single;
value414: single;
value415: single;
value416: single;
value417: single;
value418: single;
value419: single;
value420: single;
value421: single;
value422: single;
value423: single;
value424: single;
value425: single;
value426: single;
value427: single;
value428: single;
value429: single;
value430: single;
value431: single;
value432: single;
value433: single;
value434: single;
value435: single;
value436: single;
value437: single;
value438: single;
value439: single;
value440: single;
value441: single;
value442: single;
value443: single;
value444: single;
value445: single;
value446: single;
value447: single;
value448: single;
value449: single;
value450: single;
value451: single;
value452: single;
value453: single;
value454: single;
value455: single;
value456: single;
value457: single;
value458: single;
value459: single;
value460: single;
value461: single;
value462: single;
value463: single;
value464: single;
value465: single;
value466: single;
value467: single;
value468: single;
value469: single;
value470: single;
value471: single;
value472: single;
value473: single;
value474: single;
value475: single;
value476: single;
value477: single;
value478: single;
value479: single;
value480: single;
value481: single;
value482: single;
value483: single;
value484: single;
value485: single;
value486: single;
value487: single;
value488: single;
value489: single;
value490: single;
value491: single;
value492: single;
value493: single;
value494: single;
value495: single;
value496: single;
value497: single;
value498: single;
value499: single;
value500: single;
value501: single;
value502: single;
value503: single;
value504: single;
value505: single;
value506: single;
value507: single;
value508: single;
value509: single;
value510: single;
value511: single;
value512: single;
end;
begin
var valueT: r1;
var referenceT := new single[512];
var lc := 1000000000;
var sw := new System.Diagnostics.Stopwatch;
sw.Start;
loop lc do
begin
valueT.value113 := valueT.value384;
end;
sw.Stop;
writeln(sw.Elapsed);
sw.Restart;
loop lc do
begin
referenceT[112] := referenceT[383];
end;
sw.Stop;
writeln(sw.Elapsed);
readln;
end.
У меня, в режиме без отладочной информации, выводит следующее:
То есть массив, хоть он и должен ещё проверять выход индекса за границы - он всё равно немного быстрее. Хотя это немного странно, value
-тип должен быть быстрее, хоть на сколько то.
А, понятно. Я в IL коде посмотрел - там чтоб получить поле записи - создаёт ссылку на неё, а потом ещё ищет элемент с заданным именем в этой ссылке:
IL_0039: ldloca.s r1
IL_003b: ldloca.s r1
IL_003d: ldfld float32 Program1.r1::value384
IL_0042: stfld float32 Program1.r1::value113
А у массивов есть свои операторы:
IL_0090: ldloc.1 // numArray
IL_0091: ldc.i4.s 112 // 0x70
IL_0093: ldloc.1 // numArray
IL_0094: ldc.i4 383 // 0x0000017f
IL_0099: ldelem.r4
IL_009a: stelem.r4
Тут ldelem.r4
и stelem.r4
это операторы получения и перезаписи элемента по индексу.
И не лень ведь было
Значит, единственное, что можно сделать - работать с массивами по указателям, как в C++.