Unsafe
В стандартной библиотеке можно встретить unsafe_
функции, такие функции ориентированны на опытных пользователей и при правильном использование способы повысить производительность за счёт ручного контроля.
Преждевременная оптимизация
Преждевременная оптимизация — корень всех зол.
Старайтесь покрывать тестами участки кода, зависимые от unsafe преобразований.
Zero-cost преобразование
Основной паттерн таких функций это понятие владения (или уникальности), которое мы должны гарантировать.
Типичный пример — мы хотим преобразовать bytes
в строку. Если воспользоваться функцией Bytes.to_string
, то она создаст копию, преобразует её в строку и вернёт эту копию.
Почему жто так?
Дело в том, что bytes
это изменяемая (mutable) структура данных, а строки — нет. Из-за чего если мы не скопируем значение, то при изменение bytes
будет изменяться и строка, а это не то поведение, что мы хотели бы, это было бы нарушением абстракций.
Демонстрация:
let () =
let b = Bytes.make 10 'a' in (* "aaaaaaaaaa" *)
let s = string_of_bytes b in (* "aaaaaaaaaa" *)
Bytes.set b 2 'B';
print_endline s (* aaBaaaaaaa *)
Но если мы уверены и готовы гарантировать, что исходный объект не будет изменяться, то можем сделать unsafe_to_string
, которые не произведет никаких новых выделений памяти, а просто вернёт новый тип для того же объекта.
Unchecked
Другой распространённый паттерн — unchecked операции, такие операции не производят никаких runtime проверок и всю обязанность перекладывают на пользователя.
Операции с коллекцией
Пример с массивом:
let () =
let arr = [|"первый"; "второй" |] in
print_endline @@ Array.unsafe_get arr 3;
Array.unsafe_set arr 3 "третий";
print_endline @@ Array.unsafe_get arr 3
Итерация
По возможности используйте итерацию, а не явный цикл.
В ассемблере
Unchecked.
let x = Array.unsafe_get arr 1
(* movq 8(%rax), %rax *)
Checked.
let x = arr.(1)
(* movq -8(%rax), %rbx
cmpq $2047, %rbx
jbe .L104
movq 8(%rax), %rax *)
Флаг -unsafe
При использовании этого флага отключаются проверки при обращение по индексу через конструкции v.(i)
и s.[i]
. При компиляции в нативный код также отключается проверка деления на ноль.
Изменение внутреннего представления
В стандартной библиотеке есть модуль Obj
дающий операции над внутреннем представлением OCaml-значений. Not for the casual user!
Мат. часть
Смотрите RWO, Memory Representation of Values и Interfacing C with OCaml.
Transmute
Мы можем интерпретировать одно значение как другое, либо преобразовать его в полиморфную форму, посредством функции Obj.magic
:
# Obj.magic 121;;
- : 'a = <poly>
# (Obj.magic 121 : char);;
- : char = 'y'
# (Obj.repr 121 |> Obj.obj : char);;
- : char = 'y'
Юзкейс с файловыми дескрипторами:
# (Obj.magic Unix.stdout : int);;
- : int = 1
# (Obj.magic 1 : Unix.file_descr);;
- : Unix.file_descr = <abstr>