Русский
Русский
English
Статистика
Реклама

Nim

Перевод Стоит ли переходить с Python на Nim ради производительности?

22.07.2020 14:19:29 | Автор: admin
Nim это сочетание синтаксиса Python и производительности C



Несколько недель назад я бродил по GitHub и наткнулся на любопытный репозиторий: проект был полностью написан на языке Nim. До этого я с ним не сталкивался, и в этот раз решил разобраться, что это за зверь.

Сначала я подумал, что отстал от жизни, что это один из распространённых языков программирования, который многие, в отличие от меня, активно используют. И тогда я решил изучить его.


Вот какие выводы я сделал:

  • Этот язык на самом деле популярен среди узкого круга лиц.
  • Возможно, так и должно быть.

Итак, немного расскажу о моём опыте работы с Nim, коротко расскажу об особенностях программирования на нём, а также попробую сравнить его с Python и C. Забегая вперёд, отмечу, что этот язык кажется мне очень перспективным.

Код в студию!


В качестве примера я решил написать на Nim нечто более сложное, чем hello, world:



Вроде бы ничего лишнего, правда? Он кажется настолько простым, что вы без усилий сможете понять, что он делает, даже если вы никогда раньше не слышали о Nim. (Программа выведет 5 i: 5.)

Итак, давайте разберём то, что откуда-то кажется нам знакомым.

Объявление переменных


Это до боли знакомо разработчикам JavaScript. В то время как некоторые языки используют var, а некоторые используют let, JS и Nim позволяют использовать при объявлении переменных и то, и другое. Однако важно отметить, что в Nim они работают не так, как в JS. Но об этом позже.

Блоки


Чтобы обозначить новый блок в Nim, мы используем двоеточие, за которым следует строка с отступом. Всё, как в Python.

Ключевые слова


Оба цикла, а также оператор if выглядят так, как будто это фрагмент кода на Python. Фактически, всё начиная со строки 5 и далее является кодом на Python (при условии, что у нас определена функция echo).

Так что да, многие ключевые слова и операторы из Python также можно использовать в Nim: not, is, and, or и так далее.

То есть пока мы не видим в Nim ничего особенного: худшая версия Python (с точки зрения
синтаксиса), с учётом того, что для объявления переменных нужно использовать let или var.

На этом можно было бы остановиться, но есть большое но: Nim статически типизированный язык, который работает почти так же быстро, как язык C.

Ну, теперь другой разговор. Давайте проверим это.

Тест производительности





Прежде чем углубиться в синтаксис Nim (особенно в статически типизированную часть, которую мы до сих пор не видели), давайте попробуем оценить его производительность. Для этого я написал наивную реализацию для вычисления n-го числа Фибоначчи в Nim, Python и C.

Чтобы всё было по-честному, я стандартизировал реализацию на основе решения Leetcode (Вариант 1) и постарался как можно строже придерживаться её на всех трёх языках.

Вы, конечно, можете напомнить мне про LRU Cache. Но сейчас моя задача использовать стандартный подход, а не пытаться оптимизировать вычисления. Поэтому я выбрал наивную реализацию.

Вот результаты для вычисления 40-го числа Фибоначчи:



Да, строго говоря, нельзя назвать эксперимент чистым, но это коррелирует с результатами других энтузиастов, которые делали более серьёзные тесты [1][2][3].

Весь код, который я писал для этой статьи, доступен на GitHub, включая инструкцию о том, как провести этот эксперимент.

Так почему же Nim работает намного быстрее, чем Python?

Ну, я бы сказал, что есть две основные причины:

  1. Nim компилируемый язык, а Python интерпретируемый (подробнее об этом здесь). Это означает, что при запуске программы на Python выполняется больше работы, поскольку программу необходимо интерпретировать перед тем, как она сможет выполняться. Обычно из-за этого язык работает медленнее.
  2. Nim статически типизирован. Хотя в примере, который я показал ранее, не было ни одного объявления типа, позже мы увидим, что это действительно статически типизированный язык. В случае с Python, который динамически типизирован, интерпретатору нужно проделать гораздо больше работы, чтобы определить и соответствующим образом обработать типы. Это также снижает производительность.


Скорость работы растёт скорость кодинга падает


Вот что в Python Docs говорится об интерпретируемых языках:
Интерпретируемые языки обычно имеют более короткий цикл разработки / отладки, чем компилируемые, хотя их программы обычно работают медленнее.

Это хорошее обобщение компромисса между Python и C, например. Всё, что вы можете сделать на Python, вы можете сделать и на C, однако ваша программа на С будет работать во много раз быстрее.

Но вы будете тратить гораздо больше времени на написание и отладку своего кода на C, он будет громоздким и менее читабельным. И именно поэтому C уже не так востребован, а Python популярен. Другими словами, Python гораздо проще (сравнительно, конечно).

Итак, если Python находится на одном конце спектра, а C на другом, то Nim пытается встать где-то посередине. Он работает намного быстрее, чем Python, но не так сложен для программирования, как C.

Давайте посмотрим на нашу реализацию вычисления чисел Фибоначчи.

С:

#include <stdio.h>int fibonacci(int n) {    if (n <= 1) {        return n;    }     return fibonacci(n-1) + fibonacci(n-2);}int main(void) {    printf("%i", fibonacci(40));}


Python:

def fibonacci(n):    if n <= 1:        return n    return fibonacci(n-1) + fibonacci(n-2)print(fibonacci(40))


Nim:

proc fibonacci(n: int): int =     if n <= 1:        return n    return fibonacci(n-1) + fibonacci(n-2)echo(fibonacci(40))


Хотя у Nim в синтаксисе процедуры (функции) используется знак =, в целом писать код намного проще, чем на C.

Может быть, это действительно достойный компромисс? Немного сложнее писать, чем на Python, но работает в десятки раз быстрее. Я мог бы с этим смириться.

Синтаксис Nim



import strformat# Пример со страницы https://nim-lang.org/type  Person = object    name: string    age: Natural # Возраст должен быть всегда положительным числомlet people = [  Person(name: "John", age: 45),  Person(name: "Kate", age: 30)]for person in people:  echo(fmt"{person.name} is {person.age} years old")


Просто укажу на ключевые особенности.

Переменные


Для объявления переменных используем var, let или const.

var и const работают так же, как в JavaScript, а с let другая история.

let в JavaScript отличается от var с точки зрения области видимости, а let в Nim обозначает переменную, значение которой не может измениться после инициализации. Мне кажется, это похоже на Swift.

Но разве это не то же самое, что и константа? спросите вы.

Нет. В Nim различие между const и let заключается в следующем:
Для const компилятор должен иметь возможность определять значение во время компиляции, тогда как для let оно может быть определено во время выполнения.

Пример из документации:

const input = readLine(stdin) # Error: constant expression expectedlet input = readLine(stdin)   # всё правильно

Кроме того, переменные можно объявлять и инициализировать так:

var   a = 1   b = 2   c = 3   x, y = 10 # Обе переменные x и y получили значение 10


Функции


Функции в Nim называются процедурами:

proc procedureName(parameterName: parameterType):returnType =   return returnVar

Учитывая, что язык во многом похож на Python, процедуры кажутся немного странными, когда вы впервые их видите.

Использовать = вместо { или : явно сбивает с толку. Всё обстоит немного лучше с записью процедуры в одну строку:

proc hello(s: string) = echo s

Вы также можете получать результат выполнения функции:

proc toString(x: int): string =   result =       if x < 0: negative       elif x > 0: positive       else: zero

Такое чувство, что всё равно нужно как-то вернуть result, но в данном случае result не является переменной это ключевое слово. Так что, приведённый выше фрагмент кода будет правильным с точки зрения Nim.

Вы также можете перегружать процедуры:

proc toString(x: int): string =       result =             if x < 0: "negative"             elif x > 0: "positive"             else: "zero"  proc toString(x: bool): string =       result =             if x: "yep"             else: "nope"echo toString(true) # Выведет "yep"echo toString(5) # Выведет "positive"

Условия и циклы


Здесь много общего с Python.

# if true:# while true:# for num in nums:

Для перебора списка, например, вместо range() можно использовать countup(start, finish), или countdown(start, finish). Можно поступить ещё проще и использовать for i in start..finish

Пользовательский ввод и вывод



let input = readLine(stdin)echo input

Если сравнивать с Python, то readLine(stdin) эквивалентно input(), а echo эквивалентно print.

echo можно использовать как со скобками, так и без них.

Моя задача сделать так, чтобы у вас сложилось базовое представление о Nim, а не пересказывать всю его документацию. Поэтому я заканчиваю с синтаксисом и перехожу к другим особенностям языка.

Дополнительные особенности



Объектно-ориентированное программирование


Nim не объектно-ориентированный язык, но в нём реализована минимальная поддержка работы с объектами. До классов Python ему, конечно, далеко.

Макросы


Nim поддерживает макросы и метапрограммирование, и, кажется, разработчики достаточно сильно акцентируют на этом внимание. Этому посвящён собственный раздел серии из трёх уроков.

Маленький пример:

import macros  macro myMacro(arg: static[int]): untyped =     echo argmyMacro(1 + 2 * 3)


Базовые типы данных



string, char, bool, int, uint и float.

Также можно использовать эти типы:

int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64

Кроме того, в Nim строки являются mutable-типами, в отличие от Python.

Комментарии


В отличие от Python, в Nim для мультистрочных комментариев используется символ # в сочетании с [ и ].

# a comment#[amultilinecomment]#


Компиляция JavaScript


Nim может транслировать свой код в JavaScript. Не уверен, что многие заходят это использовать. Но есть вот такой пример браузерной игры Змейка, написанной на Nim.

Итераторы


Итераторы в Nim больше похожи на генераторы в Python:

iterator countup(a, b: int): int =   var res = a   while res <= b:       yield res       inc(res)


Чувствительность к регистру и нижнее подчёркивание
Nim чувствителен только к регистру первого символа.

То есть, HelloWorld и helloWorld он различает, а helloWorld, helloworld и hello_world нет. Поэтому без проблем будет работать, например, такая процедура:

proc my_func(s: string) =   echo myFunc("hello")


Насколько популярен Nim?





У Nim почти 10000 звезд на GitHub. Это явный плюс. Тем не менее, я попытался оценить популярность языка по другим источникам, и, конечно, она не так высока.

Например, Nim даже не упоминался в 2020 Stack Overflow Survey. Я не смог найти вакансии для разработчиков Nim в LinkedIn (даже с географией Worldwide), а поиск по тегу [nim-lang] на Stack Overflow выдал только 349 вопросов (сравните с ~ 1 500 000 для Python или с 270 000 для Swift)

Таким образом, было бы справедливо предположить, что большинство разработчиков не использовали его и многие никогда не слышали про язык Nim.

Замена Python?


Буду честен: я считаю Nim достаточно крутым языком. Чтобы написать эту статью, я изучил необходимый минимум, но этого хватило. Хотя я не слишком углублялся в него, я планирую использовать Nim в будущем. Лично я большой поклонник Python, но мне также нравятся языки со статической типизацией. Поэтому для меня улучшение производительности в некоторых случаях более чем компенсирует небольшую синтаксическую избыточность.

Хотя базовый синтаксис очень похож на Python, он более сложный. Поэтому большинство фанатов Python, скорее всего, не заинтересуются им.

Кроме того, не стоит забывать про язык Go. Я уверен, что многие из вас думали именно об этом во время чтения, и это правильно. Несмотря на то, что синтаксис Nim ближе к синтаксису Python, по производительности он конкурирует именно с языками а-ля упрощённый C++.

Я в своё время тестировал производительность Go. В частности, для фибоначчи (40) он работал так же быстро, как C.

Но всё-таки: может ли Nim конкурировать с Python? Я очень сомневаюсь в этом. Мы наблюдаем тенденцию роста производительности компьютеров и упрощения программирования. И, как я уже отмечал, даже если Nim предложит хороший компромисс по соотношению синтаксиса и производительности, я не думаю, что этого достаточно, чтобы победить чистый и универсальный Python.

Я общался с одним из разработчиков Nim Core. Он считает, что Nim больше подходит для тех, кто переходит с C ++, чем для питонистов.

Может ли Nim конкурировать с Go? Возможно (если Google разрешит). Язык Nim такой же мощный, как и Go. Более того, в Nim лучше реализована поддержка функций C / C ++, в том числе макросы и перегрузка.

Но об этом как-нибудь в следующий раз.



На правах рекламы


Эпичные серверы это доступные виртуальные серверы с процессорами от AMD, частота ядра CPU до 3.4 GHz. Максимальная конфигурация позволит оторваться на полную 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe. Поспешите заказать!

Подробнее..

Введение в ARCORC в Nim

16.10.2020 08:10:13 | Автор: admin

Nim переходит к более эффективным моделям управления памятью: ARC и ORC. Давайте узнаем, как именно они изменят работу с памятью в нём.



Введение


Всем привет! В этой статье я постараюсь рассказать, что такое ARC и ORC и как они повлияют на производительность или другие части Nim'а. Я не буду глубоко погружаться в аспекты программной части, а постараюсь дать более или менее высокоуровневое объяснение.


Давайте начнём издалека: Nim всегда был языком со сборщиком мусора (GC). Конечно же GC можно выключить, но тогда при работе с большей частью стандартной библиотеки (а она немаленькая) память будет утекать.


Стандартным GC в Nim уже долгое время является refc (отложенный подсчёт ссылок с mark & sweep фазой для сборки циклов), хотя доступны и другие варианты, как markAndSweep, boehm, go, regions.


За последние несколько лет у разработчиков Nim'а появилось несколько разных идей, связанных с деструкторами, собственными ссылками (owned ref) и так далее:



Из симбиоза этих идей получилось то, что в Nim называется ARC


Что такое ARC?


По своей сути ARC это модель управления памятью, основанная на автоматическом подсчёте ссылок (Automatic Reference Counting) с деструкторами и семантикой перемещений (move semantics). Можно заметить то, что ARC в Nim называется так же, как ARC в Swift, но есть одно больше различие в Nim ARC не использует атомарный подсчёт ссылок.


Подсчёт ссылок остаётся одним из самых популярных алгоритмов для освобождения неиспользуемых ресурсов программы. Счётчик ссылок для любой управляемой (контролируемой runtime) ссылки показывает количество раз, которые ссылка используется в других частях программы. Когда этот счётчик становится нулевым, все данные по этой ссылке освобождаются.


Основным отличием ARC от остальных GC в Nim является то, что ARC полностью детерминированный компилятор автоматически вставляет деструкторы в программу для удаления переменных (строк, последовательностей, ссылок, и т.д), которые больше не нужны. В этом смысле ARC похож на C++ с его деструкторами (RAII)


Для того, чтобы продемонстрировать этот процесс, мы используем интроспекцию ARC expandArc, которая будет доступна в Nim 1.4.


Возьмём простой пример кода на Nim:


proc main =   let mystr = stdin.readLine()  case mystr  of "привет":    echo "Здравствуйте!"  of "пока":    echo "Удачи!"    quit()  else:    discardmain()

И используем эту интроспекцию командой nim c --gc:arc --expandArc:main example.nim.


--expandArc: mainvar mystrtry:  mystr = readLine(stdin)  case mystr  of "привет":    echo ["Здравствуйте!"]  of "пока":    echo ["Удачи!"]    quit(0)  else:    discardfinally:  `=destroy`(mystr)-- end of expandArc ------------------------

Результат этой интроспекции довольно интересен Nim завернул тело процедуры main в try: finally выражение (код в finally выполняется всегда, даже если внутри блока try было вызвано исключение) и вставил вызов =destroy для строки mystr, чтобы она уничтожилась после окончания её жизненного цикла.


Благодаря этому мы можем увидеть одну из главных возможностей ARC: управление памятью на основе областей видимости (scope-based MM). Область видимости это любой отдельный регион кода в программе. Такое управление памятью означает, что компилятор автоматически вставит вызовы деструкторов везде, где это необходимо, после выхода из области видимости. Многие части Nim'а вводят новые области видимости: процедуры, функции, конвертеры, методы, конструкции с block, циклы for и while и так далее.


У ARC к тому же имеются так называемые hooks специальные процедуры, которые можно привязывать к типам для того, чтобы перезаписать стандартное поведение компилятора при деструкции/перемещении/копировании переменной. Они являются очень полезными при создании нестандартных семантик для своих типов, работы с низкоуровневыми операциями, включающими сырые указателями, или для FFI.


По сравнению с refc ARC обладает немалым количеством преимуществ (включая упомянутые выше):


  • Управление памятью на основе областей видимости (деструкторы вставляются после областей видимости) уменьшает потребление памяти программой и улучшает производительность.


  • Семантики перемещений возможность компилятора статически анализировать программу и переводить копии в перемещения там, где это возможно.


  • Общая куча в отличие от refc, у которого куча отдельная для каждого потока (thread-local heap), в ARC потоки имеют доступ к одной и той же памяти. Благодаря этому не нужно копировать переменные между потоками вместо этого их можно перемещать. Так же стоит обратить внимание на RFC об изоляции и отправке данных между потоками, которое строится на основе ARC.


  • Упрощение работы с FFI к примеру, с refc необходимо явно инициализировать его в каждом "чужом" (т.е. не созданным в самой программе) потоке, что не нужно для ARC. Это так же означает, что ARC является намного лучшим выбором для создания общих библиотек, которые будут использоваться из других языков (.dll, .so, нативные модули для Python'а и так далее)


  • Подходит для программирования в системах реального времени hard realtime


  • Избавление от копий (copy elision), в Nim так же называется как вывод курсоров (cursor inference) позволяет компилятору заменять копии простыми курсорами (алиасами) в большом количестве случаев



В целом, ARC для программ на Nim является отличным шагом вперёд, делающим их быстрее, уменьшающим потребление памяти, и давая им предсказуемое поведение.


Для того, чтобы включить ARC для вашей программы, всё, что нужно сделать, это скомпилировать её с ключом --gc:arc, или добавить его в конфигурационный файл вашего проекта (.nims или .cfg).


Проблема с циклами


Но подождите! Разве мы что-то не забыли? ARC проводит подсчёт ссылок, и, как известно, подсчёт ссылок не может освобождать циклы. Цикл это отношение нескольких объектов, когда они все зависят друг от друга, и эта зависимость замкнута. Возьмём простой пример цикла: 3 объекта (A, B, C), у каждого их которых есть ссылка на другой объект, создают такую зависимость:



Для того, чтобы найти и собрать данный цикл, нам необходим сборщик циклов специальная часть рантайма, которая находит и удаляет циклы, более не нужные для выполнения программы.


В Nim'е сборка циклов совершалась mark & sweep фазой refc GC, но лучше использовать ARC как основу для создания чего-то лучшего. Это приводит нас к:


ORC сборщик циклов для Nim


ORC является совершенно новым сборщиком циклов, основанным на ARC.
Его можно считать полноценным GC, так как он включает в себя фазу локального отслеживания (local tracing phase) в отличие от большинства других отслеживающих GC, которые проводят глобальное отслеживание (global tracing).
Для работы с async в Nim необходимо использовать ORC, потому что асинхронность в Nim'е образует циклы, которые необходимо собрать.


ORC сохраняет большую часть преимуществ ARC кроме детерминированности (частично) по умолчанию у ORC есть адаптивный лимит для сборки циклов, и hard realtime (тоже частично) по той же самой причине.
Для включения ORC вам нужно компилировать вашу программу с --gc:orc, но планируется, что в будущем ORC станет стандартным GC в Nim'е


Я заинтересовался! Как мне можно их протестировать?


ARC уже доступен в релизах Nim 1.2.x, но из-за большого количества недоработок лучше дождаться релиза Nim 1.4, в котором ARC и ORC будут доступны для широкого тестирования. Однако, если вам хочется протестировать их уже сейчас, то вы можете попробовать релиз-кандидат версии 1.4.


Это всё! Спасибо за чтение данной статьи я надеюсь, что она вам понравилась!


Источники / дополнительная информация:


Подробнее..

Перевод Ускоряем код на Python с помощью Nim

20.02.2021 12:17:51 | Автор: admin

Привет, хабровчане. В преддверии старта курса "Python Developer. Basic" подготовили для вас перевод интересной статьи. Также приглашаем на открытый вебинар Карьера для "Python Developer. Basic".


Python один из самых популярных и доступных языков программирования, но далеко не самый быстрый. Многие создатели библиотек и фреймворков прибегали к использованию расширения на С, чтобы их код работал быстрее, чем код на нативном Python.Этот способ вполне рабочий, но если вы не знакомы с С, сборка мусора и управление памятью станут вашим адом на Земле. И тут на сцену выходит Nim.

Что такое Nim?

Nim статически типизированный, компилируемый, объектно-ориентированный язык программирования. Nim создавался, чтобы быть таким же быстрым как С и таким же выразительным как Python, и к тому же, расширяемым как Lisp. Благодаря синтаксическому сходству с Python, Nim станет отличным выбором языка для расширения, если с C вам не по пути.

Начало работы с Nim

Чтобы начать писать на Nim, его нужно установить в свою систему. Скачайте и установите его с сайта nim-lang.org.

Как только вы закончите, мы начнем писать свой первый код на Nim. Во-первых, мы создаем файл, я назову его hello.nim, а вы можете назвать его как захотите. Теперь мы откроем его в любом текстовом редакторе и напишем:

static:    echo("Hello, world!")

Сохраните и закройте файл, затем откройте терминал из текущей директории и введите:

nim compile hello.nim

И вуаля! В консоль выведется Hello, World!. После получения первого представления о Nim перейдем к основной теме статьи.

Встраиваем Nim в приложения на Python

Nim поставляется с модулем nimpy и nimporter, которые доступны для Python.Последний можно установить с помощью pip install nimporter. Эти два пакета будут иметь важное значение при совместной работе двух языков.

Чтобы продемонстрировать возможности Nim, мы создадим простой тест, который будет сравнивать скорость работы обоих языков, на примере функции находящей n-ое число последовательности Фибоначчи.

Давайте создадим папку под названием Benchmark с 3 файлами внутри:

  • main.py файл, который мы будем запускать

  • nmath.nim файл с версией функции fib на Nim

  • pmath.py файл с версией функции fib на Python

Сначала напишем функцию fib на Python:

def fib(n):    if n == 0:        return 0    elif n < 3:        return 1    return fib(n - 1) + fib(n - 2)

А теперь переместимся в nmath.nim. Для начала нам нужно импортировать nimpy:

import nimpy

Прямо как в Python, не так ли? А теперь сама функция:

import nimpyproc fib(n: int): int {.exportpy.} =    if n == 0:        return 0    elif n < 3:        return 1    return fib(n-1) + fib(n-2)

Давайте разберемся

Мы определяем функцию fib с помощью ключевого слова proc. Дальше указываем тип возвращаемого значения как целочисленный, а (вау, что это такое?) {.exportpy.} сигнализирует Nim, что эта функция предназначена для использования в другом модуле Python. В остальном все также, как в Python.

Тестирование на время

В main.py создадим простой бенчмарк:

import nimporterfrom time import perf_counterimport nmath  # Nim imports!import pmathprint('Measuring Python...')start_py = perf_counter()for i in range(0, 40):    print(pmath.fib(i))end_py = perf_counter()print('Measuring Nim...')start_nim = perf_counter()for i in range(0, 40):    print(nmath.fib(i))end_nim = perf_counter()print('---------')print('Python Elapsed: {:.2f}'.format(end_py - start_py))print('Nim Elapsed: {:.2f}'.format(end_nim - start_nim))

Вот и все!

Пакет importer позволяет импортировать Nim в обычные модули Python, которые будут использоваться также, как и собственные. Круто, не правда ли?

Чтобы запустить код, просто введите python main.py в командную строку и смотрите, что будет!

Python Elapsed: 33.60Nim Elapsed: 1.05

Заключение

Вот и все, что нужно сделать, чтобы быстро встроить Nim в Python. Nim оказался примерно в 30 раз быстрее, но имейте ввиду, что разница в скорости может варьироваться в зависимости от выполняемой задачи. Чтобы узнать больше о Nim, рекомендую ознакомиться с официальной документацией и почитать про него в Википедии.

Что ж, на этом я закончу свой туториал! Спасибо, что дотерпели до конца. Надеюсь, эта статья окажется для вас полезной.


Узнать подробнее о курсе "Python Developer. Basic".

Смотреть запись открытого demo-урока Три кита: map(), filter() и zip().

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru