Lua vs. JavaScript

    SmallPic

    Недавно я написал пост о том как сделать рейтрейсер. Код рейтрейсера тогда был написан на JavaScript. Мне стало интересно, как с этой же задачей справится Lua, а именно LuaJIT 2.0. Ниже результаты сравнения.



    Участие принимали Chrome 9.0 Beta, Opera 11.01, Firefox 4.0 Beta 9, Explorer 9 Beta и LuaJIT 2.0 Beta 5. Все пять участников рендерили одну и ту же сцену на экран 1000x1000 по три луча на пиксель. Вот эта сцена:

    BigImage

    Результаты следующие (RPS означает «число лучей в секунду»):

    Chrome 20,400 RPS
    Opera 15,700 RPS
    Firefox 9,300 RPS
    Explorer 9,000 RPS
    LuaJIT 5,000 RPS


    Неожиданный результат: LuaJIT оказался самым медленным, причём с отрывом от остальных участников.

    Как заметил FreeLancer, можно изменить настройки сборщика мусора в Lua. Если дописать в начало кода строчку collectgarbage('setpause', 2000) то сборщик мусора будет редко прерывать работу рейтрейсера и скорость вырастет до 25,000 RPS, но пиковое потребление памяти вырастет до 1,5 гигабайт. Chrome «съел» всего лишь 150 Мб когда показал результат 20,400 RPS.

    Здесь можно скачать код примеров: JavaScript и LuaJIT. Код JS и Lua идентичен, за исключением мелких синтаксических различий. Чтобы запустить пример на Lua у себя, нужно сделать себе файл luajit.exe (взять с сайта LuaJIT готовый или скомпилировать из исходников) и выполнить в командной строке
    luajit main.lua

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 91

      +2
      Неплохо было бы написать версии браузеров. Результаты в Firefox 3.6 и 4 могут сильно отличаться.
        +2
        Учитывая, что LuaJIT позиционируется как высокопроизводительный инструмент для 3D моделирования и игр, то это очень весьма-весьма неожиданно.
        В очередной раз порадовала скорость JS в Хроме :)
          +6
          Только инструмент не для рейтрейсинга, а для написания различных скриптов для ide, внутренней игровой логики, может быть анимации.
          +4
          Удивило, что LuaJIT отстал даже от Internet Explorer.
            –7
            Может вы как-то не в курсе, но 9-й эксплорер показывает неплохую скорость на фоне остальных браузеров.
              0
              Не хватает версий…
                –2
                Версий чего? Я не понял. Если версий браузеров по сравнению с которыми IE9 показывает неплохую скорость, то вы сейчас показываете полное незнание вопроса.

                Этим сравнениями весь интернет завален. Там последние версии браузеров.
                  0
                  Версий браузеров в топике. IE9 ещё не вышел, интуитивно предполагается что сравнивался восьмой. Отсюда и удивление.
                    +3
                    А это?
                    > Участие принимали Chrome 9.0 Beta, Opera 11.01, Firefox 4.0 Beta 9, Explorer 9 Beta и LuaJIT 2.0 Beta 5.
            +2
            Lua создавался как дополнительный язык и не предназначен для написание рейтрейсерор, он скорее предназначен чтобы писать расширения части приложений, например логику или задания для игры, а не полноценные приложения.
              +16
              JavaScript создавался как дополнительный язык и не предназначен для написание рейтрейсерор, он скорее предназначен чтобы писать расширения части приложений, например логику или задания для игры, а не полноценные приложения.
                –2
                Теперь с приходом HTML5, JavaScript хотят превратить в полноценный язык для написания Веб-приложений, и как ни странно ребятам из WHATWG это удалось.
                  +1
                  Вы заблуждаетесь, JavaScript в WHATWG не меняли.
                  0
                  Зачем тогда Lua, если есть JavaScript?
                    +5
                    Зачем язык X, если есть язык Y. Это универсальный вопрос и пытаться дать на него ответ бессмысленное занятие.
                    • UFO just landed and posted this here
                        0
                        Только вот незадача-то, lua на два года старше JavaScript.
                        • UFO just landed and posted this here
                            0
                            Если я правильно понял, то ваша фраза про «поделие времен жестокого прессинга некрософтом нетскейпа» переводится что-то вроде «в те далёкие-далёкие времена, к которым lua отношения не имеет».

                            Теперь оказывается, что всё наоборот и это всё равно не имеет значения. Забавно.
                              –1
                              Он имеет ввиду, что первая версия javascript была реализован Netscape за 7 дней, без особого проектирования. Ходит такая байка.
                                0
                                Какая странная версия. Она противоречит всему, что я знаю об этом языке.
                                  –1
                                    0
                                    Странно, а везде написано, что JS создавали три человека. Исходник переведённой статьи недоступен (надо будет поискать копии), а процитированные фразы выносят мне мозг.

                                    Например: «что-то вроде PHP, только еще хуже. Его босс Netcsape быстро «зарубил» (в июле 1995, если мне не изменяет память; я сдлелал JS в начале/середине мая), т.к. это был уже третий язык после Java и JS. Было и так трудно обосновать то, что у нас 2 новых языка программирования для web.»

                                    Такое ощущение, что речь идёт о каком-то ещё языке, который был уже после Java и JS. О каком? Так как это выдрано из контекста, в комментариях к статье люди подумали, что это относится к JavaScript.

                                    Я знаю, что был язык до JS, который назывался сначала Cmm, а потом стал называться ScriptEase (очень странное название, рифмующееся у меня со striptease).
                                      –1
                                      > О каком?
                                      Как я понял, о PHP. Его, PHP, босс быстро «зарубил».
                      –2
                      Между Lua и JS есть важное отличие: Lua-программа может взаимодействовать с ОС (записывать файлы например), а также Lua-машину можно расширить любым кодом на любом языке (можно dll подключить и вызывать функции из этой dll прямо из кода на Lua). JS ограничен рамками браузера.
                        +3
                        JS ограничен рамками браузера.
                        Толко если это JS в браузере :)
                          0
                          Потому я сейчас и собираюсь запустить тестовый код на node :)
                            0
                            Тестовый код из этого топика? А смысл? Нода ни чуть ни быстрее v8 в Хроме. Разве что вы перепишете часть кода на си и встроите в ноду.
                              0
                              Не будет постоянного вывода на экран, так что должно быть чуть быстрее.
                              Заодно проверю как node-canvas работает.
                          +1
                    +1
                    Как фанат языка Lua, ни за что не поверю в данные результаты, где то есть подводный камень, который всё это объясняет.
                      +4
                      Я тоже фанат Lua и очень хочу найти этот подводный камень :) Этот рейтрейсер на Lua я написал не просто так: я хочу сделать рейстрейсер 4-х мерного пространства, а там нужна производительность хотя бы 1,000,000 RPS. Мне казалось, Lua будет в 20 раз быстрее JS, а оказалось…
                        0
                        Скорее всего в Lua неоптимально реализованы математические функции. Может это «исследование» позволит выявить «слабое звено» и оптимизировать LuaJIT
                          0
                          Рейтрейсеру нужно только уметь делать 4 арифметические операции и находить квадратный корень. Все эти пять действий вычисляются процессором, так что их не получится неоптимально реализовать. Другое дело сама среда Lua — сборщик мусора, создание новых объектов, обращения к полям таблиц — может иметь какие то особенности которых я не знаю.
                            0
                            Вот этот трюк вам не поможет улучшить скорость?

                            en.wikipedia.org/wiki/Fast_inverse_square_root
                              +1
                              Для чистоты эксперимента его нужно будет делать и для JS и для Lua.
                                +2
                                Поможет, но когда я напишу рейтрейсер на C :) Тот хак для квадратного корня заточен под разрядность числа, а в JS я не знаю сколько бит в числах.
                                  0
                                  Это довольно сложный. Смотря в каких :) Например, любая битовая операция переводит число в integer.
                                0
                                Интересно что, по тестам shootout.alioth.debian.org/u64/performance.php?test=mandelbrot&sort=elapsed LuaJit лишь немногим медленнее С++. Для построения множества мандельброта тоже требуется лишь небольшое множество математических операций.
                                  0
                                  Впрочем, там похоже сильно оптимизирован алгоритм.
                              +1
                              Проекцию из 4D в 2D? А какая там будет логика z-буфера? Или речь идет о проекции трехмерного сечения 4D?
                                +6
                                Нет, именно всего 4D на 2D. Сделать проекцию трёхмерного среза 4D на 2D можно уже сейчас: нужно просто заменить строку vec.dim = 3 на vec.dim = 4. Получится такая картинка:

                                3dslice

                                Увидеть все 16 вершин не получится: они не лежат в трёхмерном пространстве.

                                Проецирование 4D я собираюсь делать в два шага: сначала спроецирую 4D на трёхмерный куб (аналог 2-мерного монитора) и затем этот куб спроецирую на плоский экран. Но делать это буду наоборот: буду выпускать лучи из камеры (то место откуда смотрит 3-мерный наблюдатель) лежащей в одном 3-мерном срезе с кубом, лучи будут проходить через куб и через точки пересечения луча с кубом я буду запускать лучи из второй камеры (глаз четырёхмерного наблюдателя) которые пересекаясь с тем 3-мерным кубом ровно в одной точке будут сталкиваться с предметами на сцене (с тессерактом). Это требует N3 лучей где N — ширина кубического монитора. Учитывая, что лучи преломляются и отражаются в среднем 300 раз, надо отрендерить 300•N3 лучей, 0.3 триллиона.
                                  0
                                  Понятно. Z-буфер будет на обеих проекциях? Или только на первой, а визуализация 3D будет полупрозрачными объектами?
                                  Играясь с сочетаниями перспективных проекций 4D->3D->2D (точнее, я начинал даже с 5D), я обнаружил странный факт: результат можно описать формулами X=x/w, Y=y/w в подходящей прямоугольной системе координат, т.е. это будет сочетанием центральной и параллельной проекций! Но что там с z-буфером, я еще не разбирался. Кажется, роль «глубины» будет играть z-координата, вдоль которой идет параллельная проекция. Но надо смотреть подробнее.
                                    +1
                                    Я может глупость спрошу, но разве в рей-трейсинг есть Z-буфер?
                                      0
                                      Может, и нет. Имелось в виду, как будут взаимодействовать объекты, проектирующиеся в одну точку: будет ли один из них перекрывать другой, и если да — в каком порядке (ведь в точку там проецируется 2D-плоскость!), или они будут как-то сливаться (как полупрозрачные облака)
                                        +1
                                        Я наверно запутанно описал свою идею. Она же, но немного по другому.

                                        Пусть у нас есть тессеракт T в 4-х мерном пространстве. Выберем трёхмерный куб M3 — монитор 4-х мерного наблюдателя. Возьмём камеру C не лежащую в трёхмерной плоскости с M3. Спроектируем T на M3 с помощью C и получим проекцию T' — она лежит внутри M3. Теперь возьмём наш двумерный монитор M2 и камеру C2 так, чтобы из этой камеры было видно через M2 проекцию T'. Спроектируем T' на M2 и получим T'' — это то что мы увидим на экране. Реализация этого алгоритма будет делать всё в обратном порядке: выпускать луч из C2, находить пересечение с T', выпускать луч из C через точку пересечения и находить пересечение с T.
                                          0
                                          Нет, эту идею я понял хорошо — я этот вариант проекции называю «трехмерной сетчаткой». Но дело в том, что как бы мы не реализовывали проекцию 4D на 2D, у нас каждой точке экрана будет соответствовать двумерная плоскость в 4D. Часть этой плоскости будет «невидимой», а остальные точки неизбежно будут как-то отсортированы по отношению «кто кого перекрывает». Например, в случае «трехмерной сетчатки» картина будет такой:
                                          Видимая область для данной точки экрана — угол с вершиной в камере C. Границы угла будут определяться положением камеры C2 (в этом месте я не уверен). Если записать точку внутри этого угла в полярных координатах (r,a), то для двух точек (r1,a1) и (r2,a2) видимой будет та, для которой a1<a2 или (a1=a2 и r1<r2). То есть основным будет порядок по углу (проекция C2), а вторичным — порядок по дальности (проекция C).
                                          Может быть, пересечение модели с углом и ближайшую точку можно будет найти быстрее, чем трассируя все лучи этого угла?
                                            0
                                            Еще немного смущает наличие «монитора M3». Если действовать так, как Вы описали, то получится изображение куба, «вырезанного» из некоторой трехмерной среды. Будут видны его грани, где в разрезе покажут «внутренние» детали изображения (нет, 4D-наблюдатель будет считать их внешними, но мы обычно можем видеть лишь «силуэты» того, что видит он). Это действительно соответствует замыслу? Или в идеале хотелось бы получить проекцию того, что наблюдатель увидел бы на бесконечном дисплее (со зрением, охватывающим 180 градусов)?
                                              0
                                              И есть у этого подхода серьезная проблема. Если таким образом попытаться получить изображение четырехмерной комнаты, то окажется, что по краям монитора M3 будут только изображения пола, стен и потолка, а все самое интересное будет спрятано внутри. Даже если съемка идет на открытом воздухе, то мы увидим, что часть объекта, находящаяся ниже уровня камеры «утонет» в земле (на фотографиях нашего имра мы видим, что часть тела человека находится на фоне земли, но флатландец, изучив эту фотографию, сказал бы, что человек утонул в земеле, а на поверхности торчит только голова, состоящая из волос и ушей.
                                              Что со всем этим делать, я так и не придумал. Поэтому идею своего 4D-вьюера/редактора мне пришлось отложить на неопределенное время.
                                            0
                                            У вас может быть много пересечений, и вы ищете ближайшее.
                                            0
                                            Я планирую обойтись без Z-буфера. Визуализацию 3D пока не знаю как лучше сделать: можно сплошной (как на рисунках) а можно и полупрозрачной (будет дольше работать). Смотря как красивее получится.
                                        +2
                                        Если нужна производительность, стоит обратить внимание на GPU. OpenCL или CUDA на приличной карточке гораздо лучше справятся с такой высокопараллельной задачей чем скриптовые языки.
                                        0
                                        Надеюсь, что дело в том, что LuaJIT сейчас в фазе стабилизации, а после фазы оптимизации ситуация улучшится.
                                        +7
                                        Теперь бы еще сюда добавить результаты на С++, Java, C# и Phyton.
                                          –1
                                          Питон? Зачем? Разве что PyPy потестить, конечно…
                                            0
                                            Для числодробилок как этот рендерер можно psyco подключить
                                              0
                                              Psyco уже сколько лет не развивают, да и PyPy, в общем-то, обогнал его уже давненько
                                            +3
                                            ссылочка в тему: www.cnblogs.com/miloyip/archive/2010/07/07/languages_brawl_GI.html

                                            там правда все по-китайски, но суть ясна и без знания китайского: человек тоже какой-то рендерер написал на куче языков; можно иероглифы пропускать и в пролетарскую суть диаграм с результатами замеров вникать ;-)
                                            0
                                            ИМХО дело в том, сколько внимания уделяют языку. JavaScript развивается динамичней ибо он востребован, под него затачиваются интерпретаторы, движки браузеров; в него вкидываются деньги на порядок больше, чем в Lua, вот и результат
                                              +1
                                              Робко прошу вас задокументировать код.
                                                +1
                                                Мысль очень правильная :) Я не сделал этого по одной причине: боялся, что кто-нибудь откроет файл raytracer.js, увидит там 2000 строчек какого то текста и подумает «ну вот… а говорил, что код рейтрейсера простой — а тут как обычно манускрипт на несколько экранов», хотя большая часть текста будет на самом деле комментариями. Я однажды читал исходник алгоритма BWT написанный автором DjVu (они в свободном доступе) — там столько текста, что… Но если выкинуть комментарии, то окажется, что кода совсем немного.

                                                Мне не сложно прокомментировать: напишите только список файлов в которых вы хотите увидеть комментарии.
                                                  0
                                                  Да вроде всё понятно. Просто нужно было догадаться, что в этих языках так объекты выглядят. :)
                                                    0
                                                    raytracer.lua и screen.lua.

                                                    И в методе vec.vec похоже происходит что-то простое, но лень читать две страницы кода на lua когда вы это одним предложением по-русски можете сказать :)
                                                      0
                                                      vec.vec это векторное произведение в n-мерном пространстве. Для трёхмерного случая есть простая формула, а в общем случае это считается через определитель. Определитель можно найти методом Гаусса.
                                                      0
                                                      Прошу прощения за назойливость, но когда можно ожидать комментарии?
                                                        0
                                                        Я не заметил ваш комментарий, извиняюсь. Отправьте мне свой e-mail через ЛС и я отправлю вам эти два файла с комментариями. Обновлять zip архив не хочу, потому что я у себя что то менял в коде и сейчас bmp файлы пишутся неправильные, а разбираться в этом сейчас нет времени.
                                                    +1
                                                    вы бы еще на перле написали и пнули тушку попугая…
                                                      0
                                                      Perl, увы, не знаю. Это функционально-процедурный язык как JS? Есть ли там компилятор, по типу JS V8 или LuaJIT?
                                                      • UFO just landed and posted this here
                                                        0
                                                        chrome9 (32b) — 16556
                                                        luajit-2.0.0-beta5 (64b) — 8881
                                                        luaj не смог запустить, org.luaj.vm2.LuaError: cubecyl.lua:37: attempt to index? (a nil value)
                                                        osx 10.6/core2duo 2.53
                                                          0
                                                          Node.js+node-canvas в среднем чуть медленнее, но не больше 5%.
                                                          GC: 9.0.597.84 beta
                                                          Node: v0.3.7
                                                          node-canvas: 0.4.3
                                                            +4
                                                            Intel Core i5 750, 2.67GHz
                                                            Windows 7 Ultimate

                                                            chrome9 (9.0.597.84) — 22129
                                                            luajit-2.0.0-beta5 — 23946

                                                            изменения в коде луа:
                                                            — все вызовы глобальных функций переделаны на вызов локальных функций:
                                                            local bitband, bitlshift = bit.band, bit.lshift
                                                            — понижена цикличность сборки мусора
                                                            collectgarbage(«setpause», 600)

                                                              0
                                                              см. habrahabr.ru/blogs/algorithm/113250/#comment_3635646

                                                              промазал по кнопке :-(
                                                                +1
                                                                Впечатляет. Я ещё заметил, что Lua тратит много времени на заполнение массива в screen.render:

                                                                this.pixels[index] = color
                                                                


                                                                Если убрать эту строчку, а также вывод процентов через io.write, то скорость возрастает более чем в три раза. Надо заметить, что для JS это не важно: там вывод на экран и сохранение пикселей не влияет на скорость. Как сказать Lua, что this.pixels это массив фиксированной длины?..
                                                                  +1
                                                                  Впечатляет. Я ещё заметил, что Lua тратит много времени на заполнение массива в screen.render:

                                                                  this.pixels[index] = color


                                                                  трудно сказать в чем дело, но если заменить эту строчку на
                                                                  this.pixels[index] = index
                                                                  


                                                                  то RPS увеличивается до 41491

                                                                  Или снова сборщик мусора, или не совсем оптимальный алгоритм хеширования в таблице.
                                                                    +2
                                                                    или JIT выкидывает часть вычислений — color-то теперь никуда не утекает ;-)
                                                                0
                                                                все вызовы глобальных функций переделаны на вызов локальных функций


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

                                                                понижена цикличность сборки мусора


                                                                да, встроенный GC в Lua при «мусора много и мрет он молодым» ведет себя хуже GC V8.
                                                                  0
                                                                  если вы действительно все вызовы заменяли, то ради чистоты эксперимента следовало бы аналогичную операцию и с JavaScript проделать.


                                                                  Запустил Lua тест с оригинальными файлами без локальных функций. Изменений особых нет +- пару процентов.

                                                                  да, встроенный GC в Lua при «мусора много и мрет он молодым» ведет себя хуже GC V8.


                                                                  Да, сборка мусора в хроме отменная: скрипт добирается до 200Мб, потом через несколько секунд чистит мусор до 96мб, и снова увеличивается в размере. Насколько я помню, в v8 сборщик многопоточный, видимо тут выигрыш.
                                                                  С луа все гораздо хуже: если шаг сборки 600, то сборщик видимо вообще не срабатывает, кушая 500мб. Если шаг 300, то потребление памяти медленно растет до 230 мб.
                                                                    +1
                                                                    Запустил Lua тест с оригинальными файлами без локальных функций. Изменений особых нет +- пару процентов.


                                                                    Т.е. если функции вызывать глобально, а сборку мусора отложить, то получается быстро?

                                                                    Ага! Ну я так и думал, что в GC дело. От LJ2 следует ожидать, что он сам поднимет загрузку глобалов (load hoisting). На обычных числодробилках даже Crankshaft (V8 после 3.0) к LuaJIT2 вроде не подобрался пока, я уж не говорю о классическом бэкенде (V8 до 3.0).

                                                                    Насколько я помню, в v8 сборщик многопоточный


                                                                    Нет, он однопоточный (точнее они, для частичных сборок в молодом поколеннии копирующий scavenger, для полных сборок — MarkSweep/MarkCompact). Ну и пауза в несколько секунд — это, м-м-м-м, не сказать что бы отменный сборщик :-)
                                                                  0
                                                                  Участие принимали Chrome 9.0 Beta


                                                                  Хм. А ведь это с V8 2.5.x, т.е. без Crankshaft, т.е. без серьезной адаптивной компиляции.

                                                                  Как бы не оказалось, что тут все упирается исключительно в GC.
                                                                    0
                                                                    Если быть более точным, я использовал 9.0.597.86 beta. Хром говорит, что это последняя версия. Где то есть ещё более продвинутая бета?
                                                                      0
                                                                      Нет, беты нет.

                                                                      Есть Chrome 10 Dev, которая вот-вот пойдет в бету.
                                                                        +1
                                                                        Неплохо.
                                                                        9.0.597.84: 15150
                                                                        10.0.648.18: 21590
                                                                    +1
                                                                    Если вы перепишите код на С++ то я готов помочь портировать на C# для Silverlight (WriteableBitmap) и WinForms (GDI+).
                                                                    Очень интересно сравнить скорость.
                                                                      +1
                                                                      Договорились :)
                                                                      +2
                                                                      Тред про этот хабратопик в Луашной рассылке:

                                                                      thread.gmane.org/gmane.comp.lang.lua.general/75252

                                                                      Мнение Майка Палла, автора LuaJIT:

                                                                      article.gmane.org/gmane.comp.lang.lua.general/75265
                                                                        0
                                                                        Я бы не догадался написать «a = ffi.new('double[?]', ...)» вместо «a = {}». Это какая то фича LuaJIT — в документации Lua я такого не встречал. Вообще было бы неплохо, если бы Mike написал подобные простые сценарии кода.

                                                                        Я могу дать ему инвайт, если он захочет зарегистрироваться.
                                                                          0
                                                                          Да, это фича LuaJIT 2, начиная с beta6. См. luajit.org/ext_ffi.html и соседние топики в меню слева.

                                                                          Майк сожалеет, но у него сейчас недостаточно времени, чтобы отвечать на Хабре.

                                                                          Он читает письма и отвечает на вопросы в официальной рассылке Луа (на английском): www.lua.org/lua-l.html

                                                                          По-русски вопросы Майку можно задавать, например, через меня.
                                                                            +1
                                                                            Почитал про эту фичу, впечатляет. Однако я вынужден не согласиться с Майком по поводу использования таких методов при сравнении. Ниже ответ ему.

                                                                            Hello Mike,

                                                                            Today I suddenly found from agladysh that you know about my simple raytracer and you even analysed my code. You found that the code produces lots of temporary objects (that become garbage almost immediately after creation) and that the code dynamically inserts pixels to a Lua-table. These two things make the program extremely slow. Almost exactly the same code I've written in JavaScript — it also produces lots of temporary objects and inserts pixels one-by-one to an array — and this code appeared much faster under Chrome than the Lua code under LuaJIT.

                                                                            You offered a simple solution: to replace the standard and simple array «pixels = {}» with a special array «pixels = ffi.new(...)». Your solution makes the program much faster — even faster than JS under Chrome. But from this point we're comparing not JS and Lua but JS and Lua+LowLevelFeatures. To make the comparsion correct, we must allow to use in JS features like WebGL and that'd make the comparsion meaningless — I didn't want to know what is faster WebGL or FFI.

                                                                            It'll be more honestly to rewrite the Lua code so as it'll not create tons of objects with short life, but this thing must be done in both Lua and JS code and it's unclear who will appear faster: LuaJIT or JS V8.

                                                                            ankh1989@habrahabr.ru
                                                                              0
                                                                              Рекомендую всё-таки отправить этот ответ в луашную рассылку. Иначе очень странная переписка получится :-)
                                                                        0
                                                                        спасибо за вашу статью!

                                                                        Only users with full accounts can post comments. Log in, please.