Comments 74
Таким образом, проверка события на null и вызов его обработчика будут выполнены в виде одной команды, что обеспечит безопасный вызов события.
Там не по этой причине nullRef не упадет. Это код будет преобразован в сохранение поля в локальную переменную, последующей проверкой локальной переменной и её же вызов. Т.к. это локальная переменная то у нас есть гарантия что ее никто не поменяет после проверки, в отличии от поля.
Там все еще хуже. "Стандарт" C# не запрещает оптимизатору использовать вместо поля локальную переменную или наоборот когда он уверен, что там одинаковые значения — если только поле не объявлено как volatile. Слово "стандарт" я взял в кавычки — потому что полного стандарта у C# нет, особенно в части модели памяти, вместо этого есть куча постов в блогах у авторов языка.
Поэтому, формально даже код с явным "вытягиванием" в локальную переменную может работать некорректно!
var unload = this.unload; // Оптимизатор имеет право "выкинуть" эту переменную и использовать поле
if (unload != null) unload();
Но, "по счастливой случайности", все известные реализации оптимизируют доступ к полям через локальные переменные, а не наоборот (иначе бы дырку быстро заткнули). По той же причине явно неправильный код может нормально работать:
if (this.unload != null) this.unload(); // Есть шанс, что в результате оптимизации ошибка пропадет
Появление оператора безопасной навигации в этом плане — не только удобный синтаксический сахар — но и спасение от проблемы неоднозначности стандарта, ведь этот оператор действительно гарантирует вызов метода лишь при непустом значении левого выражения:
this.unload?.Invoke(); // Некуда оптимизировать
this.unload?.Invoke();
Возможно сорсы движка компилируются другой версией дотнета, но та версия, которая используется для компиляции исходников разработчика (собственно, завязанная на версию моно) еще не умеет такие конструкции.
Ну, можно компилировать компилятором, идущим в составе Microsoft Build Tools 2015 — главное же получить бинарник, дальше версия языка не имеет значения, как и версия рантайма. Ну или самому компилятор собрать — исходный код Roslyn, кажется, открыт.
И поскольку сам компилятор написан на C# — его можно попытаться запустить через тот же Mono...
Но это я так, в порядке шутки.
лавное же получить бинарник, дальше версия языка не имеет значения, как и версия рантайма.
Абсолютно неверно. Советую попробовать скомпилировать coroutine в рантайме юнити и в рантайме C# .net и посмотреть рефлектором / испаем — абсолютно разный код. По той же причине неправильно будут работать BeginInvoke и тп штуки если их скомпилить свежим рантаймом в dll и положить в юнити. Проблема — в патченном моно из юнити.
Но это же не означает, что они несовместимы!
Есть такая библиотечка — https://github.com/sta/websocket-sharp, реализующая вебсокеты поверх штатных сокетов. Ну и до юнити5 сокеты были отрублены. Ну как отрублены — их криворукие индусы в билд-процедуре проверяли типы в клиентской сборке — не начинаются ли они с System.Net.Sockets и прерывать процесс с эксепшном «насяльника, купите прошку, кушать-ма ощинь хоцца». И тут же проверяли whitelist сборок, в которых разрешены были эти типы. Теперь уже можно рассказать очень простой способ обхода этого запрета — просто берешь и делаешь неймспейс, начинающийся с Systemxxx + даешь имя сборки с таким же именем (у меня была SystematicLaziness.dll :) ) и тест проходит, те в такой сборке можно было гонять сокеты без проблем. Вот берешь эту MIT-овскую библиотечку, меняешь неймспейс, компилишь — все вроде работает в редакторе, а вот на реальных девайсах начинаются чудеса (особенно после AOT тогда еще на ios). Собственно, автор подхачил ее на совместимость с моно (проблема была в вызове асинхронных методов) и выложил на ассетстор — в реп фиксы не пошли, но можно было погуглить и найти решение.
Я компилил async / await для .NET 2.0 — сомневаюсь, что под измененный Mono это будет сложнее.
Он ничем не отличается от кода под 4.5
Надо просто подсунуть нужные классы, не важно в какой сборке. Я их в то время писал сам — но сейчас советую просто вытащить нужные файлы из исходников Mono или CoreCLR
Заработает, если добавить в проект требуемые классы. Точно так же, как у меня все заработало в честном 2.0
Да, у меня в проекте был класс Task в пространстве имен System.Threading.Tasks. Ну и что?
Кто из нас слепой? https://github.com/mono/referencesource/tree/mono-4.4.0-branch/mscorlib/system/threading/Tasks
Разумеется, я не имел в виду вытащить класс из той же самой версии Mono, которая его не имеет! Его надо вытащить в виде исходного кода из той версии, в которой этот класс есть, и положить среди своих файлов.
Еще раз — в юнити mono 2.6.3 до сих пор
Уверен, что не я. Апгрейд компилятора до моно4.4 только планируется в юнити5.5, когда будет — неизвестно, возможно перенесут на 5.6 и тд.
Да какая разница, какая там версия?! Вам что, религия не позволяет взять пару исходников из другой версии?
Затем, что разговор исходно шел именно про возможность замены компилятора, на что вы возразили, что async/await не будет работать.
Так вот — будет, если подложить ему кучку файликов.
Я компилил async / await для .NET 2.0
Я это правда делал. С именем сборки никакой проблемы не возникло — компилятор его не проверяет. Совсем не проверяет.
PS могу даже привести список классов, которых достаточно чтобы компилятор смог скомпилировать любой код, использующий async/await:
System.Threading.Tasks.Task
System.Threading.Tasks.Task<>
System.Runtime.CompilerServices.AsyncVoidMethodBuilder
System.Runtime.CompilerServices.AsyncTaskMethodBuilder
System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>
- TaskAwaiter — полный путь и имя не важны, этот объект должен вернуть метод
Task.GetAwaiter()
Последние 4 типа можно сделать структурами для экономии памяти.
Разумеется, если копировать их из Mono — придется скопировать еще несколько, которые используются внутри. А из CoreCLR без изменений скопировать и вовсе не получится — там часть плясок вокруг ExecutionContext придется вырезать.
Ну и про «а вдруг оно поменяется из другого потока» — юнити в паблик-апи вся исключительно однопоточная + внутренние проверки на вызов только из основного потока. Если кто-то решил запилить свой код в другом потоке (а это он должен сделать специально), то пусть сам и обеспечивает целостность и непротиворечивость.
Еще автор статьи наверняка бы предложил «оптимизацию» конструкции «var t = a != null? a: b» в «var t = a ?? b», абсолютно не вникая в перегрузку операторов в кастомной реализации моно юнити (жаль, что не встретил, а то было бы совсем замечательно).
Вывод — меньше рекламы своего поделия, больше изучения предмета тестирования.
Вникать в каждый проект у нас, к сожалению, нет никаких сил.
Прекратите тогда вводить людей в заблуждение разбором «ошибок», потому что каждый конкретный продукт может содержать особенности реализации, в которые вы не собираетесь вникать, но с удовольствием расскажите, что ваша тулза нашла ошибку. По сути вы делаете себе антирекламу подобными статьями и комментариями.
А что там не так со скоростью?
enum KeyType {
One,
Two
}
var dict = new Dictionary<KeyType, int>() {
{ KeyType.One, 1},
{ KeyType.Two, 2},
};
foreach (var pair in dict) {
Debug.Log(pair.key + " " + pair.value);
}
По вашей ссылке нашел вот такое описание бага: http://www.gamasutra.com/blogs/WendelinReich/20131109/203841/C_Memory_Management_for_Unity_Developers_part_1_of_3.php, где есть вот это уточнение:
[EDIT] Matthew Hanlon pointed my attention to an unfortunate (yet also very interesting) discrepancy between Microsoft's current C# compiler and the older Mono/C# compiler that Unity uses 'under the hood' to compile your scripts on-the-fly. You probably know that you can use Microsoft Visual Studio to develop and even compile Unity/Mono compatible code. You just drop the respective assembly into the 'Assets' folder. All code is then executed in a Unity/Mono runtime environment. However, results can still differ depending on who compiled the code! foreach loops are just such a case, as I've only now figured out. While both compilers recognize whether a collection's GetEnumerator() returns a struct or a class, the Mono/C# has a bug which 'boxes' (see below, on boxing) a struct-enumerator to create a reference type.
Если в переводе на русский язык и коротко — то это баг компилятора, а не рантайма. Замена компилятора устраняет утечку памяти.
"У них" — да. Но мне все больше нравится идея использовать другой компилятор для своего проекта, если когда-нибудь буду его делать.
Что меня больше напрягает — так это то, что если юнитеки повысят версию C# для рантайма — им придется долго и нудно допиливать фичи новых версий языка в il2cpp, да и то это будет неполноценно, никаких dynamic и прочих плюшек.
Что меня больше напрягает — так это то, что если юнитеки повысят версию C# для рантайма — им придется долго и нудно допиливать фичи новых версий языка в il2cpp, да и то это будет неполноценно, никаких dynamic и прочих плюшек.
Ну с чего бы? Какой бы версии ни был компилятор — на выходе у него будет корректный IL. И если il2cpp оправдает свое название — то он этот самый IL "проглотит" независимо от того кто его нагенерил.
Тот же dynamic в конечном итоге сводится к рефлексии. Если il2cpp умеет рефлексию — сумеет и dynamic.
На моно остались только десктопы, но и они скорее всего будут переведены на il2cpp. Про то, что моно останется — юнитеки говорили только про редактор. Потому и наплевать, лишь бы il2cpp в поном объеме поддерживал трансляцию.
il2cpp на android — основной с 5.4Где вы видели про *основной*? С него всего лишь сняли пометку «экспериментально». Это одна из двух опций, они одинаково основные пока что. Хотя тут утверждать не буду.
В WebGL другого варианта, кроме IL2CPP, по сути, и нет — не портировать же Моно…
А вот десктопы совершенно точно будут поддерживать как il2cpp, так и Моно в качестве рантайма, и юнитеки это говорили. Пруф можно найти тут в комментах тут:
https://blogs.unity3d.com/ru/2014/05/20/the-future-of-scripting-in-unity/
The Mono JIT will remain available within the Editor and Standalone.
SEBASTIANO & ZIMM there are more details for this topic on the forum. Basically, the thought is IL2CPP will come to Standalone players someday; but it is not a priority and it will not replace Mono but co-exist as a build option.
С него всего лишь сняли пометку «экспериментально»
Так по сути это и сделало его основным — годным для продакшна. еще пара минорных версий фиксов и моно думаю будут помечать как deprecated, чтобы не тянуть 2 рантайма под одну платформу.
1) Меньше итоговый размер билда.
2) Сборка быстрее на порядок.
3) AOT-компиляция автоматически лишает крутых штук типа Expression и System.Reflection.Emit, полагающихся на JIT-компиляцию.
Еще автор статьи наверняка бы предложил «оптимизацию» конструкции «var t = a != null? a: b» в «var t = a ?? b», абсолютно не вникая в перегрузку операторов в кастомной реализации моно юнити (жаль, что не встретил, а то было бы совсем замечательно).
А что можно перегрузить в этом выражении? Разве тернарный оператор перегружается? И зачем это может понадобится делать?
т.е. есть вот такой удобный кейс:
var rb = GetComponent<RigidBody>() ?? gameObject.AddComponent<RigidBody>();
Вот оно будет работать совсем не так, как задумано в «классическом» поведении.
Интересный подход, хотя, как я понял, используется только в редакторе. Так что по сути если бы анализатор указал на это место, то был бы прав. правда, это добавило бы странных глюков при отладке :) Интересно, а почему в Unity не могли переопределить и оператор ??
?
Погодите. Перегрузили же не оператор приведения типа, а оператор сравнения. Это значит, что во время компиляции компилятор знает, что за объект он с null-ом сравнивает. Что мешает компилятору на этапе компиляции посмотреть, что слева от оператора ??
специальный тип и реализовать этот оператор корректно?
Насколько я понял, перегрузка оператора сделана не средством языка C# (где возможно перегружать некоторые операторы), а хаком в компиляторе (иначе непонятно, что там делать в компиляторе, если сравнение и так можно перегрузить). Поэтому мне совершенно непонятно, что мешает сделать хак законченным и хакать также синтаксическую конструкцию ??
, у которых тип слева входит в некий магический список классов, использовать один алгоритм сравнения, а для всех прочих — Object.ReferenceEquals
.
this.unload?.Invoke();
Развернется в:
IL_0001: ldsfld class [mscorlib]System.Action ThirtyNineEighty.Program::Event // считываем 1 раз
IL_0006: dup // дублируем значение
IL_0007: brtrue.s IL_000c
IL_0009: pop
IL_000a: br.s IL_0012
IL_000c: callvirt instance void [mscorlib]System.Action::Invoke()
IL_0011: nop
IL_0012: br.s IL_0014
IL_0014: ret
Что соответствует тому что я написал. За исключением того, что здесь нет дополнительной локальной переменной. А используется дублирование значения записанного на стек, после считывания поля. Но это уже детали, главное что поле считывается 1 раз, а дальше идет работа с локальным значением.
Это в байт-коде поле считывается всего 1 раз, но после преобразования в машинный код теоретически может получиться что угодно. Кроме того, то, что вы показали — это поведение текущей версии конкретного компилятора, я же говорил про спецификацию языка.
x86
Event?.Invoke();
01652D90 mov ecx,dword ptr ds:[400AE28h] // считываем 1 раз
01652D96 test ecx,ecx // проверка на налл
01652D98 jne 01652D9B
01652D9A ret
01652D9B mov eax,dword ptr [ecx+0Ch] // достаем копию
01652D9E mov ecx,dword ptr [ecx+4]
01652DA1 call eax
01652DA3 ret
x64
00007FFE92E44CE0 sub rsp,28h
00007FFE92E44CE4 mov rcx,21B1DE44C20h // считываем 1 раз
00007FFE92E44CEE mov rcx,qword ptr [rcx]
00007FFE92E44CF1 test rcx,rcx // проверка на налл
00007FFE92E44CF4 jne 00007FFE92E44CFB
00007FFE92E44CF6 add rsp,28h
00007FFE92E44CFA ret
00007FFE92E44CFB mov qword ptr [rsp+20h],rcx // достаем копию
00007FFE92E44D00 mov rcx,qword ptr [rcx+8]
00007FFE92E44D04 mov rax,qword ptr [rsp+20h]
00007FFE92E44D09 call qword ptr [rax+18h]
00007FFE92E44D0C nop
00007FFE92E44D0D add rsp,28h
00007FFE92E44D11 ret
И мне совершенно не понятно, почему Вы ко мне прицепились, ведь я всего лишь указал, что это выражение не будет преобразовано в 1 команду. И пояснил, что будет практически, а не теоретически.
Как по мне то тут ничего страшного нет. Падать не будет. Неправильного поведения тоже не ожидается.
Анализируем ошибки в открытых компонентах Unity3D