MrAbad 13 Опубликовано: 1 апреля, 2021 Решил я как-то поискать методы оптимизации кода для ОпенКомпов, и Луа в целом. Но на форуме, вроде, я не нашел тему, где в одном месте сконцентрированы вещи, помогающие в оптимизации. Разве что нашел одну ветку, на заморском форуме, вот ее компиляция/перевод/адаптация/дополнение: (за помощь в переводе, спасибо KoshakLol и Google Translate) # Место в памяти Вот таблица, где есть, сколько что занимает в памяти: boolean, number и string занимают 9 байт. Указатель на функцию, или на таблицу, тоже 9 байт. Пустая функция (function() end) занимает 93 байт, пустая функция с return (function() return true end) занимает 102 байт. Пустая таблица занимает 36 байт, плюс за каждую степень двойки (2^n) еще дополнительно: 9 байт для 0-1 элементов в таблице 18 байт для 2 элементов в таблице 36 байт для 3-4 элементов в таблице 71 байт для 5-8 элементов в таблице 142 байт для 9-16 элементов в таблице 284 байт для 17-32 элементов в таблице 569 байт для 33-64 элементов в таблице и т.д. И, кстати, таблицы не сжимаются, то есть если туда запихать 1000 элементов, а потом удалить их, то весить она будет, как будто в ней все еще 1000 элементов # Сборка мусора Автоматический сборщик мусора в опенкомпах вроде бы отключен, и вызывается только через os.sleep(0), и то не гарантированно, так что разработчик мода советует его вызывать 10 раз подряд, чтоб уж точно И еще, автоматический сборщик чистит ТОЛЬКО локальные переменные # Советы сомнительной эффективности (от них я не засек прироста) Убрать из кода деление, потому что оно, якобы, медленнее, чем умножение (условно:) local a = n / 4 --> local a = n * 0.25 Заменить все условные операторы, на логические тернарные операторы, где это возможно (условно:) if (a > b) then c = a else c = b end --> c = (a > b) and a or b (Если вы не поняли, что сейчас вообще было, то в этой теме все расписано) Менять переменные местами, без буфера (Lua 5.3+) local a = 174 local b = 3 a = a ~ b b = a ~ b a = a ~ b print(a) --> 3 print(b) --> 174 (Если вы не поняли, что сейчас вообще было, то ищите битовую операцию XOR) Этот совет дает хотя бы небольшой прирост, из-за того, что нет лишний переменной, но этот плюс нивелируется, если ее удалить # В итоге Не используйте рекурсию функций, а если совсем приспичило, то почаще вставляйте сбор мусора Не используйте замыкания (это когда функция возвращает другую функцию) Старайтесь не использовать таблицы, вместо них лучше делать так: local coords = {x = 10, y = 20, z = 370} -- (72 байта) --> local x = 10 local y = 20 local z = 370 -- (27 байт) -- или вообще local xyz = 010020370 -- (9 байт) print("z = ", xyz // 1000000, "\ny = " , xyz // 1000 % 1000, "\nz = ", xyz % 1000) -- (Если вы не поняли, что сейчас вообще было, то ищите деление с остатком) Старайтесь не использовать не-локальные переменные, так как они бьют по архитектуре программы, и их не собирает гарбадж коллектор # P.S В данной заметке рассматривалась только сторона оптимизации кода, определенный код может отвратительно выглядеть, но работать быстро, и наоборот, в реальных программах лучше соблюдать баланс, между красотой, и быстродействием, в какой-нибудь программе для EEPROM'a, допустим, вообще о красоте кода не идет и речи Ну это в целом все, что я хотел рассказать, если есть исправления/уточнения/дополнения/свои_идеи, то милости прошу в комментарии 2 3 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Zer0Galaxy 2 187 Опубликовано: 1 апреля, 2021 3 часа назад, MrAbad сказал: Менять переменные местами, без буфера (Lua 5.3+) local a = 174 local b = 3 a = a ~ b b = a ~ b a = a ~ b print(a) --> 3 print(b) --> 174 А как на счет такого варианта? a,b = b,a На сколько это более/менее производительней чем пример с XORом? Во всяком случае это на много понятней и занимает меньше букв, что существенно, если программа лежит в EEPROM. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
MrAbad Автор темы 13 Опубликовано: 1 апреля, 2021 8 часов назад, Zer0Galaxy сказал: А как на счет такого варианта? a,b = b,a На сколько это более/менее производительней чем пример с XORом? Во всяком случае это на много понятней и занимает меньше букв, что существенно, если программа лежит в EEPROM. В Lua эта конструкция создает буферную переменную a, b = b, a --> local с = a a = b b = c 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
hohserg 197 Опубликовано: 1 апреля, 2021 12 часа назад, MrAbad сказал: Не используйте замыкания (это когда функция возвращает другую функцию) Почему? 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
MrAbad Автор темы 13 Опубликовано: 1 апреля, 2021 Только что, hohserg сказал: Почему? ну во-первых можно обойтись и без них, во-вторых просто оболочка функции занимает 1/10 килобайта, то есть просто замыкание, которое ничего не делает использует 2/10 килобайта. Согласен, что в некоторых ситуациях они могут быть полезны, но в том же пресловутом EEPROM'е не до красивой архитектуры с функциональным программированием 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 10 апреля, 2021 Если кратко у любителей факторки поймал вот эту книжку. Написал вот этот код: Скрытый текст local computer = require("computer") function freeMemory() local result = 0 for i = 1, 10 do result = math.max(result, computer.freeMemory()) os.sleep(0) end return result end function memoize (f) local mem = {} -- memoizing table setmetatable(mem, {__mode = "kv"}) -- make it weak return function (x) -- new version of ’f’, with memoizing local r = mem[x] if r == nil then -- no previous result? r = f(x) -- calls original function mem[x] = r -- store result for reuse end return r end end print("function") local start = os.clock() for i=1,100000 do local x = math.sin(i) --0.026591985 end print(os.clock()-start) start = os.clock() local sin = math.sin for i=1,100000 do --local x = math.sin(i) --0.026591985 local x = sin(i) --0.01030093 end print(os.clock()-start) print("tables") start = os.clock() for i = 1, 100000 do local a = {} a[1]=1; a[2] = 2; a[3] = 3 end print(os.clock()-start) start = os.clock() for i = 1, 100000 do local a = {true,true,true} a[1]=1; a[2] = 2; a[3] = 3 end print(os.clock()-start) print("fill nil") t = {} lim = 20000 for i = 1, lim do t[i] = i end start = os.clock() for k in pairs(t) do t[k] = nil end print(os.clock()-start) t = {} for i = 1, lim do t[i] = i end start = os.clock() while true do -- хорош для малых таблиц и поиска по ним. Но крашит комп при больших таблицах local k = next(t) if not k then break end t[k] = nil end print(os.clock()-start) print("time demo") start = os.clock() local t = {} for i = 1970, 2000 do t[i] = os.time({year = i, month = 6, day = 14}) end print(os.clock()-start) start = os.clock() local t = {} local aux = {year = nil, month = 6, day = 14} for i = 1970, 2000 do aux.year = i t[i] = os.time(aux) end print(os.clock()-start) print("memory demo") print("start \t \t",computer.freeMemory()) freeMemory() for i = 1, lim do t[i] = i end print("with normal table ", computer.freeMemory()) t_m = memoize(t) t = {} print("with memoryzed \t",computer.freeMemory()) И вот какой результат: Как видно некоторые моменты реально спасают а некоторые не очень. Там еще есть совет о корутинах но я не проверял. 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 10 апреля, 2021 Изначально я не хотел комментировать этот «урок», т.к. объём комментариев в несколько раз превысил бы изначальный текст. Главная претензия к нему в отсутствии конкретики и каких-либо примеров, позволяющих читателю прийти к тем же выводам. @Taruu , приведя конкретный пример, спасает этот урок. Если кто-то хочет продолжить это начинание, я прошу не скидывать все замеры в одну программу, или хотя бы в коде одной программы дробить код на блоки и добавлять короткое описание, что именно там измеряется. Например, так: Демонстрация: локальная функция sin работает в два раза быстрее, чем функция math.sin. Замедление происходит два раза: при извлечении поля math из таблицы глобальных переменных и при извлечении поля sin из таблицы math. кусочек кода Также можно привести ещё пример, демонстрирующий, что работа с глобальной переменной равносильна работе с полем таблицы. А ещё важно продемонстрировать, начиная с какого числа использований становится выгодным копировать функцию math.sin в локальную переменную. Потому что если бездумно повторять советы из этого «урока», то вместо оптимизации может получиться пессимизация. Также я советую для записи чисел с большим количеством нулей использовать научную форму. Чтобы понять, сколько нулей содержит число 100000, мне приходится задерживать взгляд на этом числе, а запись 1e5 считывается мгновенно. 3 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 10 апреля, 2021 (изменено) 2 часа назад, eu_tomat сказал: Изначально я не хотел комментировать этот «урок», т.к. объём комментариев в несколько раз превысил бы изначальный текст. Главная претензия к нему в отсутствии конкретики и каких-либо примеров, позволяющих читателю прийти к тем же выводам. @Taruu , приведя конкретный пример, спасает этот урок. Если кто-то хочет продолжить это начинание, я прошу не скидывать все замеры в одну программу, или хотя бы в коде одной программы дробить код на блоки и добавлять короткое описание, что именно там измеряется. Например, так: Демонстрация: локальная функция sin работает в два раза быстрее, чем функция math.sin. Замедление происходит два раза: при извлечении поля math из таблицы глобальных переменных и при извлечении поля sin из таблицы math. кусочек кода Также можно привести ещё пример, демонстрирующий, что работа с глобальной переменной равносильна работе с полем таблицы. А ещё важно продемонстрировать, начиная с какого числа использований становится выгодным копировать функцию math.sin в локальную переменную. Потому что если бездумно повторять советы из этого «урока», то вместо оптимизации может получиться пессимизация. Также я советую для записи чисел с большим количеством нулей использовать научную форму. Чтобы понять, сколько нулей содержит число 100000, мне приходится задерживать взгляд на этом числе, а запись 1e5 считывается мгновенно. Я больше как пример того что смог найти. Оформлять это точно еще надо (я просто протестировал наскоряк). Ихмо я бы залез на уровень языка и посмотрел почему это работает быстрее :/ Сейчас попробую оформить это хоть во что-то читабельное. Изменено 10 апреля, 2021 пользователем Taruu Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 10 апреля, 2021 (изменено) Итак делаю более структурированный пост чем был до этого... Для показания производительности используются: os.clock() - дает приблизительное время работы CPU во время исполнения программы. computer.freeMemory() - дает нам узнать сколько памяти есть у компа. Локализация функций: Делайте функции в программе локальными. Это работает как и с локализацией существующих функций. Пример с функцией math.sin и ее локальной версией: local computer = require("computer") local unicode = require("unicode") local x = 0 local sin = math.sin local start = os.clock() local memory_start = 0 for i=1,1e5 do x = math.sin(i) end print("1e5 math.sin",os.clock()-start) start = os.clock() for i=1,1e5 do x = sin(i) end print("1e5 local.sin",os.clock()-start) --Замер для редкого вызова start = os.clock() x = math.sin(3) x = math.sin(10) x = math.sin(16) x = math.sin(42) print("rare math.sin",os.clock()-start) start = os.clock() x = sin(3) x = sin(10) x = sin(16) x = sin(42) print("rare local.sin",os.clock()-start) --Замер для одиночного вызова memory_start = computer.freeMemory() -- проверка на кушание памяти start = os.clock() x = math.sin(42) print("one math.sin",os.clock()-start, "memory take", memory_start-computer.freeMemory()) x = 0 memory_start = computer.freeMemory() local sin2 = math.sin start = os.clock() x = sin2(42) print("one local.sin2",os.clock()-start, "memory take",memory_start-computer.freeMemory()) Из данных тестов видно что локализация функций имеет смысл при частом обращении, но в случае же одиночного вызова функции смысла это не придает и на ход поршней особо сильно не влияет... Так же я решил проверить данный метод на openOS библиотеке unicode local computer = require("computer") local unicode = require("unicode") local x = 0 local start = os.clock() local memory_start = 0 local un_len = unicode.len for i=1,1e5 do x = unicode.len(tostring(i)) end print("1e5 unicode.len",os.clock()-start) start = os.clock() for i=1,1e5 do x = un_len(i) end print("1e5 local.len",os.clock()-start) --Замер для редкого вызова start = os.clock() x = unicode.len("Арбуз") x = unicode.len("Помидор") x = unicode.len("Капитализм") x = unicode.len("Ракета") print("rare unicode.len",os.clock()-start) start = os.clock() x = un_len("Арбуз") x = un_len("Помидор") x = un_len("Капитализм") x = un_len("Ракета") print("rare local.len",os.clock()-start) --Замер для одиночного вызова memory_start = computer.freeMemory() -- проверка на кушание памяти start = os.clock() x = unicode.len("Hello world!") print("one unicode.len",os.clock()-start, "memory take", memory_start-computer.freeMemory()) x = 0 memory_start = computer.freeMemory() local un_len2 = unicode.len start = os.clock() x = un_len2("Hello world!") print("one local.len",os.clock()-start, "memory take",memory_start-computer.freeMemory()) Тут же прирост в производительности не особо заметен. Так что стоит отдельно проверять библиотеку или функцию на эффективность такой локализации. Так же можно локализовать и свои функции: local computer = require("computer") local x = 0 local start = 0 local memory_start = 0 memory_start = computer.freeMemory() function func1(a,b) return a + b end start = os.clock() x = func1(9,0.3) print("norm func",os.clock()-start, "memory take",memory_start-computer.freeMemory()) memory_start = computer.freeMemory() local func2 = function(a,b) return a + b end start = os.clock() x = func2(9,0.3) print("local func",os.clock()-start, "memory take",memory_start-computer.freeMemory()) start = os.clock() for i=1,1e5 do local x = func1(i,i) end print("func1 \t",os.clock()-start) start = os.clock() local sin = math.sin for i=1,1e5 do local x = func2(i,i) end print("func2 \t",os.clock()-start) Так же стоит понимать что все зависит от вашей функции, но все же можно лишний раз локализовать... Вроде на ход поршней не влияет.... Главное не локализовывать функции в программе которые вызываются один раз. Это вообще не даст ничего и даже наоборот может замедлить работу (если таких функций много). Локальные переменные Используйте локальные переменные в зависимости от своей задачи. Если вам нужно сэкономить память то создавайте локальные переменные в функциях а если вам нужна переменная которая нужна множеству функций то лучше ее объявить глобально. Локальные переменные в функциях удаляются после завершения работы функции что позволяет сэкономить место в памяти. --var_global local computer = require("computer") local memory_start = computer.freeMemory() local test_table = {1,2,3,4,5,6,7,8,9,10} function work_on_test_table() --Что то делаем с test_table end print("memory take", memory_start - computer.freeMemory()) --var_local local computer = require("computer") local memory_start = computer.freeMemory() function work_on_test_table() local test_table = {1,2,3,4,5,6,7,8,9,10} --Что то делаем с test_table end print("memory take", memory_start - computer.freeMemory()) Подготовка объектов (таблиц) Лучше подготовить таблицу с частью необходимых значений и их уже дополнять. print("create obj") start = os.clock() local t = {} for i = 1970, 2000 do t[i] = os.time({year = i, month = 6, day = 14}) end print("direct insertion time", os.clock()-start) start = os.clock() t = {} local aux = {year = nil, month = 6, day = 14} for i = 1970, 2000 do aux.year = i t[i] = os.time(aux) end print("prepared insert time", os.clock()-start) start = os.clock() for i = 1, 1e5 do local a = {} a[1]=1; a[2] = 2; a[3] = 3 end print("num insert", os.clock()-start) start = os.clock() for i = 1, 1e5 do local a = {true,true,true} a[1]=1; a[2] = 2; a[3] = 3 end print("prepared insert", os.clock()-start) X*X вместо X^2 Если нам нужна 2 степень числа то быстрее это будет умножить чем возводить в степень, Но если у нас уже 3 степень или больше то данный финт бесполезен. local start = os.clock() for i=1,1e7 do local x = i * i end print("*",os.clock()-start) start = os.clock() for i=1,1e7 do local x = i ^ 2 end print("^",os.clock()-start) start = os.clock() for i=1,1e7 do local x = i * i * i * i * i end print("* X5",os.clock()-start) start = os.clock() for i=1,1e7 do local x = i ^ 5 end print("^ 5",os.clock()-start) Swap переменных Самый быстрый способ это использовать временную переменную, красивый и почти такой же эффективный метод перестановки переменных будет a,b = b,a Так же стоит отметить что не один из перечисленных способов не требует выделения памяти. Скрытый текст local a,b = 23,76 local computer = require("computer") local memory_start = 0 local start = 0 print("test 1e7") memory_start = computer.freeMemory() start = os.clock() for i=1,1e7 do a,b=b,a end print("swap a,b = b,a", os.clock()-start, memory_start - computer.freeMemory()) -- Результат: 0 memory_start = computer.freeMemory() start = os.clock() local temp1 = 0 for i=1,1e7 do temp1 = a a = b b = temp1 end print("temp \t",os.clock()-start, memory_start - computer.freeMemory()) memory_start = computer.freeMemory() start = os.clock() for i=1,1e7 do a=a~b b=a~b a=a~b end print("swap ~ \t",os.clock()-start, memory_start - computer.freeMemory()) print() print("test 100") memory_start = computer.freeMemory() start = os.clock() for i=1,100 do a,b=b,a end print("swap a,b = b,a", os.clock()-start, memory_start - computer.freeMemory()) -- Результат: 0 memory_start = computer.freeMemory() start = os.clock() local temp2 = 0 for i=1,100 do temp2 = a a = b b = temp2 end print("swap temp",os.clock()-start, memory_start - computer.freeMemory()) memory_start = computer.freeMemory() start = os.clock() for i=1,100 do a=a~b b=a~b a=a~b end print("swap ~ \t",os.clock()-start, memory_start - computer.freeMemory()) print() print("one swap") memory_start = computer.freeMemory() start = os.clock() a,b=b,a print("swap a,b=b,a ",os.clock()-start, memory_start - computer.freeMemory()) memory_start = computer.freeMemory() start = os.clock() local temp = 0 temp = a a = b b = temp print("swap temp",os.clock()-start, memory_start - computer.freeMemory()) memory_start = computer.freeMemory() start = os.clock() a=a~b b=a~b a=a~b print("swap ~ \t",os.clock()-start, memory_start - computer.freeMemory()) Вот все что смог более менее конкретное накопать и проверить. Прошу кинуть в лицо ошибки или вопросы. Вместе подумаем.... Теперь поправили. Изменено 11 апреля, 2021 пользователем Taruu Внесли правки по некоторым темам 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 11 апреля, 2021 Пффф.. Ну, раз я влез в эту тему, то продолжу обсуждение. 1 час назад, Taruu сказал: os.clock() - показывает как долго запущена программа Не совсем. Как долго работает компьютер, скажет computer.uptime(), а os.clock() скажет, сколько из этого времени компьютер потратил на, собственно, вычисления. Конечно, с некоторыми оговорками, но в целом именно os.clock() позволяет оценить нагрузку, создаваемую тем или иным кодом на игровой сервер. 2 часа назад, Taruu сказал: Делайте функции в программе локальными. Это работает как и с локализацией существующих функций: Пример с функцией math.sin и ее локальной версией: ... Как видно мы получили очень большой прирост в производительности. Почти в два раза! Что уже неплохо. Так же это особенность работает и на своих функциях. Это грязный эксперимент с неправильной интерпретацией результата. В нём извлечение поля из таблицы выполняется два раза, о чём не упомянуто в описании. Также эксперимент не сообщает о том, имеет ли смысл создавать локальную переменную для функции math.sin, если её планируется использовать всего два раза. Или пять, например. 2 часа назад, Taruu сказал: X*X вместо X^2 ... Во всех экспериментах маловаты измеряемые интервалы времени. Но в этом они слишком малы. Шум легко может оказаться выше реальной разницы во времени. А шум есть всегда, даже при выполнении кода на оборудовании с избыточной мощностью. 2 часа назад, Taruu сказал: Самый быстрый способ это создать временную переименую и записать одну меняемую переименую в нее. А здесь как раз именно шум не позволяет увидеть, что a,b = b,a имеет ровно ту же эффективность. Вообще, при интерпретации результатов следует сравнивать полученную разницу с шумовой, чтобы не делать преждевременных выводов. 2 часа назад, Taruu сказал: Если же мы сильно экономим память то нам отлично подойдет побитовый XOR (~) В этом месте я всё-таки ожидал увидеть экспериментальное подтверждение «эффективности» этого трюка. Но именно это заблуждение надо развеять в первую очередь. Эксперимент: local a,b = 23,76 local computer = require("computer") local mem0 = computer.freeMemory() for i=1,1e7 do a,b=b,a end print(mem0 - computer.freeMemory()) -- Результат: 0 for i=1,1e7 do a=a~b b=a~b a=a~b end print(mem0 - computer.freeMemory()) -- Результат: 6631 Результат: Обмен значений переменных через a,b=b,a потребляет ровно 0 дополнительной памяти и быстро работает. Зато обмен посредством XOR и память потребляет, и работает медленно, да ещё и код загромождает. Я не знаю, почему этот вредный совет транслируют на форумах программистов. Вне сомнений, в арсенале программиста должен быть трюк обмена переменных через исключающее или. Но зачем его тулить к месту и не к месту? Урок же вроде как про оптимизацию кода. И даже если бы этот трюк оказался эффективным, почему нет упоминания о том, что xor работает не для всех типов переменных? И раз пошёл такой разговор, разберу предыдущее обсуждение: В 01.04.2021 в 09:29, Zer0Galaxy сказал: А как на счет такого варианта? a,b = b,a На сколько это более/менее производительней чем пример с XORом? Эффективнее со всех точек зрения. В 01.04.2021 в 17:50, MrAbad сказал: В Lua эта конструкция создает буферную переменную Lua для любых операций создаёт буферные переменные для хранения временных результатов. Но, в случае a,b=b,a они уничтожаются незамедлительно в отличие от xor. Операция a,b=b,aтребует на выполнение столько же времени, как три отдельных операции присваивания, но потребления памяти не увеличивает. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 11 апреля, 2021 Скрытый текст 7 часов назад, eu_tomat сказал: Пффф.. Ну, раз я влез в эту тему, то продолжу обсуждение. Не совсем. Как долго работает компьютер, скажет computer.uptime(), а os.clock() скажет, сколько из этого времени компьютер потратил на, собственно, вычисления. Конечно, с некоторыми оговорками, но в целом именно os.clock() позволяет оценить нагрузку, создаваемую тем или иным кодом на игровой сервер. Это грязный эксперимент с неправильной интерпретацией результата. В нём извлечение поля из таблицы выполняется два раза, о чём не упомянуто в описании. Также эксперимент не сообщает о том, имеет ли смысл создавать локальную переменную для функции math.sin, если её планируется использовать всего два раза. Или пять, например. Во всех экспериментах маловаты измеряемые интервалы времени. Но в этом они слишком малы. Шум легко может оказаться выше реальной разницы во времени. А шум есть всегда, даже при выполнении кода на оборудовании с избыточной мощностью. А здесь как раз именно шум не позволяет увидеть, что a,b = b,a имеет ровно ту же эффективность. Вообще, при интерпретации результатов следует сравнивать полученную разницу с шумовой, чтобы не делать преждевременных выводов. В этом месте я всё-таки ожидал увидеть экспериментальное подтверждение «эффективности» этого трюка. Но именно это заблуждение надо развеять в первую очередь. Эксперимент: local a,b = 23,76 local computer = require("computer") local mem0 = computer.freeMemory() for i=1,1e7 do a,b=b,a end print(mem0 - computer.freeMemory()) -- Результат: 0 for i=1,1e7 do a=a~b b=a~b a=a~b end print(mem0 - computer.freeMemory()) -- Результат: 6631 Результат: Обмен значений переменных через a,b=b,a потребляет ровно 0 дополнительной памяти и быстро работает. Зато обмен посредством XOR и память потребляет, и работает медленно, да ещё и код загромождает. Я не знаю, почему этот вредный совет транслируют на форумах программистов. Вне сомнений, в арсенале программиста должен быть трюк обмена переменных через исключающее или. Но зачем его тулить к месту и не к месту? Урок же вроде как про оптимизацию кода. И даже если бы этот трюк оказался эффективным, почему нет упоминания о том, что xor работает не для всех типов переменных? И раз пошёл такой разговор, разберу предыдущее обсуждение: Эффективнее со всех точек зрения. Lua для любых операций создаёт буферные переменные для хранения временных результатов. Но, в случае a,b=b,a они уничтожаются незамедлительно в отличие от xor. Операция a,b=b,aтребует на выполнение столько же времени, как три отдельных операции присваивания, но потребления памяти не увеличивает. Сейчас поправим тогда. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc 625 Опубликовано: 11 апреля, 2021 (изменено) Изменено 11 апреля, 2021 пользователем LeshaInc Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 11 апреля, 2021 (изменено) 9 часов назад, eu_tomat сказал: В этом месте я всё-таки ожидал увидеть экспериментальное подтверждение «эффективности» этого трюка. Но именно это заблуждение надо развеять в первую очередь. Эксперимент: local a,b = 23,76 local computer = require("computer") local mem0 = computer.freeMemory() for i=1,1e7 do a,b=b,a end print(mem0 - computer.freeMemory()) -- Результат: 0 for i=1,1e7 do a=a~b b=a~b a=a~b end print(mem0 - computer.freeMemory()) -- Результат: 6631 Тут ошибочка получается. У тебя print в памяти остается ?:/ Так что каждый раз нужно в переменную записывать. local a,b = 23,76 local computer = require("computer") local mem0 = computer.freeMemory() for i=1,1e7 do a,b=b,a end print(mem0 - computer.freeMemory()) -- Результат: 0 mem0 = computer.freeMemory() for i=1,1e7 do a=a~b b=a~b a=a~b end print(mem0 - computer.freeMemory()) print() mem0 = computer.freeMemory() print("test") print(mem0 - computer.freeMemory()) И вот что мне вывело: Изменено 11 апреля, 2021 пользователем Taruu 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 11 апреля, 2021 5 минут назад, Taruu сказал: Тут ошибочка получается. У тебя print в памяти остается ?:/ Так что каждый раз нужно в переименую записывать. Да, здесь я ошибся. Обе операции не потребляют дополнительную память, но xor всё равно хуже по другим причинам. @LeshaInc своей картинкой, видимо, намекает на сложность темы, но это сразу было понятно. Тесты, измеряющие код, всегда сложно создавать, потому как кодомерка сама может вносить погрешность. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 11 апреля, 2021 @eu_tomat Вроде поправил и добавил еще тестов для более ясной картины. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 11 апреля, 2021 Осталось лишь решить проблему шума. Интервалы времени длительностью 1.6629999990414e-06 вообще ни о чём не говорят на фоне шума измерений. Надо повторять эксперимент в цикле с достаточным количеством итераций. Например, для проверки одноразового вызова math.sin я бы использовал такие циклы: for i=1,1e6 do local sin = math.sin x = sin(3) end for i=1,1e6 do x = math.sin(3) end Но этого тоже недостаточно. Есть разброс значений. Поэтому надо повторять эксперимент какое-то количество раз. Какое именно? Надо вспоминать основы статистики. И если у кого-то эти воспоминания достаточно свежи, дайте совет. Для дальнейших рассуждений по теме я порылся в своих прошлых наработках, но оказалось, что там я застрял, не дойдя до конца. Но какая-то информация есть. Сервер нагружен неравномерно. И для выравнивания замеров имеет смысл чередовать циклы экспериментов: Выполнили в цикле один код, сделали короткую паузу, выполнили цикл со вторым кодом, сделал паузу, выполнили цикл с третим, и т.д., по кругу. Сервер бывает нагружен настолько неравномерно, что некоторые замеры аномально отличаются от среднего значения в болшьую сторону, и их надо бы отбрасывать. Мы же не характеристики сервера измеряем, а сравниваем эффективность кода. Но тут снова нужно аккуратно применять статистику. В своих прошлых экспериментах я пришёл к выводу, что итоговый код окажется достаточно сложным, что его проще будет вынести в отдельную библиотеку, аккуратно выполняющую замеры. Один я не готов оценивать глубину этой кроличьей норы, но @Taruu, мы можем попробовать написать такую библиотеку вместе. Также я прошу откликнуться и других форумчан. В тонкой сфере измерений аудит кода никогда не будет лишним. Также нужны идеи. Возможно, всё можно сделать проще, чем мне кажется. А я попробую в ближайшие дни найти все старые наработки и скомпилировать их во что-то определённое. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taruu 30 Опубликовано: 12 апреля, 2021 @eu_tomat Ок. Глянь что у тебя было в наработках. Посмотрим что было и что можно придумать. А так я хз стоит ли лезть так глубоко. Ибо может оказаться что результат будет тот же. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
MrAbad Автор темы 13 Опубликовано: 16 апреля, 2021 В 10.04.2021 в 17:16, eu_tomat сказал: Изначально я не хотел комментировать этот «урок», т.к. объём комментариев в несколько раз превысил бы изначальный текст. Главная претензия к нему в отсутствии конкретики и каких-либо примеров, позволяющих читателю прийти к тем же выводам. не отрицаю, что поверхностно, это в основном просто перевод заметки с заморского форума, в будущем постараюсь что-то по-глубже делать В конце концов моя цель частично достигнута, есть тема, где есть кладесь информации, по-оптимизации кода Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 16 апреля, 2021 3 часа назад, MrAbad сказал: есть тема, где есть кладесь информации, по-оптимизации кода Это далеко не кладезь. Синтетические тесты, конечно, полезны для понимания, но их практическая ценность не высока. Нужны какие-то примеры, приближенные к реальности: программа, долго считающая что-то нужное, которая после оптимизации начинает считать хотя бы в два раза быстрее. 3 часа назад, MrAbad сказал: в будущем постараюсь что-то по-глубже делать Предлагаю начать как раз с этой темы: рассказать, что такое оптимизация, зачем нужна, и как её применять, когда улучшение одного параметра ухудшает другой. И поправить противоречивые моменты, вводящие в заблуждение. Тогда этот урок будет давать какие-то ответы, а не порождать вопросы на ровном месте. В 01.04.2021 в 06:24, MrAbad сказал: Пустая функция (function() end) занимает 93 байт, пустая функция с return (function() return true end) занимает 102 байт. Что значит "занимает"? В какой момент? В момент создания функции или на время вызова? Нужно ли вызывать сборщик мусора после вызова функции? Или сборщик мусора тут не поможет, и требуется очищать ссылку на функцию? Значит ли это, что избавление кода от функций всегда полезно для его производительности? Чему учит эта часть урока? В 01.04.2021 в 06:24, MrAbad сказал: И, кстати, таблицы не сжимаются, то есть если туда запихать 1000 элементов, а потом удалить их, то весить она будет, как будто в ней все еще 1000 элементов Значит ли это, что очистка элементов таблиц не имеет смысла? В 01.04.2021 в 06:24, MrAbad сказал: Автоматический сборщик мусора в опенкомпах вроде бы отключен, и вызывается только через os.sleep(0), и то не гарантированно, так что разработчик мода советует его вызывать 10 раз подряд, чтоб уж точно И еще, автоматический сборщик чистит ТОЛЬКО локальные переменные А если я обнулил глобальную переменную, содержащую длинную строку, разве занимаемая ей память не будет очищена сборщиком мусора? И что значит, 10 раз подряд? Что мне мешает перемежать вызовы sleep какими-то вычислениями? И что мне мешает заменить вызовы sleep, вызовами методов периферии. И обязательно ли мне каждый раз делать ровно 10 вызовов, или есть способ определить факт вызова сборщика мусора? И как определить, что вообще пора его вызывать? В 01.04.2021 в 06:24, MrAbad сказал: Не используйте рекурсию функций, а если совсем приспичило, то почаще вставляйте сбор мусора Насколько чаще? Будет ли правильным вставлять сбор мусора в каждый вызов рекурсивной функции? В 01.04.2021 в 06:24, MrAbad сказал: Не используйте замыкания (это когда функция возвращает другую функцию) Замыкание имеет чуть иной смысл. Возвращаемая функция должна использовать внешние по отношению к ней локальные переменные порождающей функции. Тогда будет замыкание. В 01.04.2021 в 06:24, MrAbad сказал: (Если вы не поняли, что сейчас вообще было, то ищите битовую операцию XOR) Этот совет дает хотя бы небольшой прирост, из-за того, что нет лишний переменной, но этот плюс нивелируется, если ее удалить Здесь я не понял, что сейчас вообще было. Откуда надо удалить переменную , чтобы этот плюс нивелировался? И что за прирост даёт этот совет? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах