Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

MyDC.ru _ Программирование на Lua _ Способы Повышения Производительности

Автор: Setuper 3.1.2009, 15:02

Довольно давно на форуме летали мысли по созданию подобной темы. В данной теме предполагается раскрывать как общие методы оптимизации, которые применяются ко всем lua скриптам, так и методы оптимизации конкретных кусков кода.

Итак, немного слов о том как работает lua.
LUA - это скриптовый язык программирования, то есть для работы ему не обязательна предварительная компиляция. "Загрузка" lua кода осуществляется интерпретатором. Существуют и скомпилированные lua коды. Компиляция осуществляется с помощью инструмента luac.

Преимущества скомпилированного кода:




Основные методы оптимизации.

При разработке программ на Lua нужно иметь в виду, что компилятор этого языка не обладает широкими способностями по оптимизации. С другой стороны, интерпретатор Lua реализован очень эффективно и чаще всего обеспечивает весьма приличную скорость работы. Если же нужна сверхвысокая производительность, то мы должны сами следить за тем, чтобы в коде не было множества лишних конструкций. Добиться этого будет легче, если мы будем придерживаться нескольких простых правил при написании кода на Lua.

  1. Доступ к локальным переменным в Lua несколько быстрее, чем к глобальным. Если требуется интенсивный доступ к глобальной таблице или функции, лучше завести локальную, присвоить ей значение глобальной и только потом начинать использовать. Эта техника не несет накладных расходов на копирование, так как в Lua данные таких типов копируются как ссылки, а не как значения.
    Код
    t = {"1", "2", "3", "4", "5"}
    function test()
      local temp, i = t, 0
      while i < 6 do
        Core.SendToAll(temp[i])
        i = i + 1
      end
    end


  2. По возможности в коде, критичном по производительности, не создавай множество объектов Lua, управляемых сборщиком мусора. Такие объекты создаются при склеивании строк, вызове конструкторов таблиц (например при вызовах функций с переменным числом аргументов), при объявлении любых функций, при выполнении команд dofile/dostring. Не следует дробить строки и лишний раз использовать конкатенацию, то есть вместо кода
    Код
    "test1".."\r\n".."test2"
    следует использовать код
    Код
    "test1\r\ntest2"
    В первом случае в lua стеке до следующей уборки мусора останутся строки: "test2", "\r\n" и "test1\r\ntest2", во втором же случае в стеке останется всего лишь строка "test1\r\ntest2". Подумайте над этим! Это не пустые слова. Каждый может проверить данное утверждение, используя lua debagger.

  3. Вместо многочисленных конкатенаций можно использовать метод format. Использование метода не будет оставлять много мусора. Например, вместо того, чтобы писать
    Код
    local str = "String: "..sParam1..", "..sParam2..", "..sParam3..", "..sParam4
    можем написать
    Код
    local str = ("String: %s, %s, %s, %s"):format(sParam1, sParam2, sParam3, sParam4)
    Для незначительных конкатенаций (менее 4 строк ("String: ", ", ", ", ", ", ") ) выигрыша нет, но для множества конкатенаций данный метод сэкономит память. Рекомендуется для конкатенации менее 4 временных строк использовать оператор конкатенации (..), а для множественной конкатенации использовать метод (format).
    Давайте разберём, что в данных 2 случаях записывается в мусор:
    1)
    Код
    local str = "String: "..sParam1..", "..sParam2..", "..sParam3..", "..sParam4
    В мусоре будет следующее:
    Код
    tostring(sParam4)
    ", "
    tostring(sParam3)
    ", "
    tostring(sParam2)
    ", "
    tostring(sParam1)
    "String: sParam1, sParam2, sParam3, sParam4"

    2)
    Код
    local str = ("String: %s, %s, %s, %s"):format(sParam1, sParam2, sParam3, sParam4)
    В мусоре будет следующее:
    Код
    "String: sParam1, sParam2, sParam3, sParam4"
    tostring(sParam4)
    tostring(sParam3)
    tostring(sParam2)
    tostring(sParam1)
    "String: %s, %s, %s, %s"
    "String: sParam1, sParam2, sParam3, sParam4"


  4. Одним из способов оптимизации является вариант использования для строк локальных методов, вместо глобальных функций. Например, использовать метод:
    Код
    sData:find(sStr)
    вместо функции глобальной таблицы
    Код
    string.find(sData, sStr)


  5. Перевод скриптового кода в код на C/C++ - один из действенных методов оптимизации. Если вдруг показалось, что какой-то участок кода на Lua нужно подвергнуть интенсивной оптимизации, обнаружен первый сигнал к тому, что этот кусок кода нужно переписать на C++.

  6. Большинство команд протокола NMDC оканчивается на символ "|", который служит для разделения команд, когда они находятся в одном потоке или в одной строке. Поэтому эффективнее отсылать 1 раз единую строку с командами, нежели отсылать несколько раз отдельные команды.
    Код
    local t = {
      "$MyINFO $ALL nick1 $ $ $ $0$",
      "$MyINFO $ALL nick2 $ $ $ $0$",
      "$MyINFO $ALL nick3 $ $ $ $0$"
    }
    local sStr = ""
    for i, v in ipairs(t) do
      sStr = (sStr == "" and "" or sStr.."|")..v
    end
    Core.SendToAll(sStr)
    тоже самое касается и сообщений (если вместо команд отправляются сообщения).

  7. Если в таблице используются только целочисленные индексы, то в цикле следует использовать функцию ipairs, в противном случае использовать функцию pairs
    Код
    local t1 = {
      "1",
      "2",
      "3"
    }
    for i, v in ipairs(t1) do
      ...
    end
    local t2 = {
      a = "1",
      b = "2",
      c = "3"
    }
    for i, v in pairs(t2) do
      ...
    end

  8. По возможности используйте метод match для строк вместо метода find. В условиях (логических выражениях) используйте метод find, во всех других случаях используйте метод match.

  9. По возможности подключайте файлы с помощью функции require, а не с помощью функции dofile.
    Функция require выполняет файл однажды и передаёт загруженные параметры по ссылке.
    Функция dofile выполняет файл каждый раз и передаёт загруженные параметры по значению.
    При этом стоит учитывать, что функция require ищет загружаемые lua модули по путям, которые хранятся в переменной package.path (для загрузки dll библиотек package.cpath), а функция dofile загружает файлы по полному пути, который вы указываете перед названием файла.



Управление памятью

Язык Lua автоматически управляет памятью при помощи сборщика мусора (garbage collector): интерпретатор периодически вызывает сборщик мусора, удаляющий объекты, для которых была выделена память (таблицы, userdata, функции, потоки и строки) и которые стали недоступными из Lua («мертвые» объекты).

Интерпретатор Lua задает предел объема памяти (threshold), занимаемого данными. Как только занятый объем достигнет этого предела, запускается алгоритм, освобождающий память, занятую накопившимися «мертвыми» объектами. Затем устанавливается новый предел, равный двукратному объему памяти, занимаемой после очистки.

Чтобы запустить сборку мусора немедленно, нужно программно установить предел занимаемой памяти в 0 (вызвав lua_setgcthreshold() из C или collectgarbage() из Lua). Чтобы остановить сборку мусора, устанавливаем этот предел в достаточно большое значение.

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

При написании программ на Lua обязательно учитывай то, каким образом интерпретатор Lua управляет распределением памяти. Сборка мусора в версии 5 — относительно затратная по производительности операция (используется алгоритм non-incremental mark and sweep). Она инициируется автоматически, когда объем выделенной памяти превышает двукратный объем памяти, оставшейся выделенной после предыдущей сборки мусора (объем выделенной памяти, при превышении которого произойдет следующая сборка мусора, также можно задавать программно). Значит, фактически, в достаточно большой динамической системе сборка мусора может быть запущена в произвольный момент времени, вызвав просадку по производительности. Чем больше памяти выделено под объекты Lua, тем дольше происходит сбор мусора.

Итак, если мы, например, читаем в Lua данные из файла построчно и склеиваешь их в одну строку линейно, скорее всего, процесс будет продвигаться страшно медленно: после каждой следующей операции склейки объем занятой памяти, как минимум, удваивается и, соответственно, запускается процесс сборки мусора.

Код создает новую строку (и присваивает ее переменной some_string), а строка, хранившаяся в some_string до склейки, остается в памяти до следующей сборки мусора:
Код
local some_string = some_string.."a"

Автор: Wariner 3.1.2009, 18:19

Сразу вопрос, интерпретатор переводит скрипт в бинарный вид один раз при загрузке или каждый раз при обращении к скрипту?

Автор: Setuper 3.1.2009, 19:26

Конечно же 1 раз при загрузке скрипта. Весь загруженный бинарный код находится в ОЗУ до тех пор пока скрипт не будет остановлен или перезапущен.

Автор: Wariner 3.1.2009, 20:35

А смысл ли его тогда компилировать?! Или если скрипт скомпилированный то в памяти он хранится не будет?

Автор: Setuper 3.1.2009, 20:46

я же написал чем отличается скомпилированный код от нескомпилированного.
1) Выполняется простейшая оптимизация.
2) Скомпилированный код быстрее загружается в память.

Автор: Setuper 11.1.2009, 19:43

Вместо кода:

Код
string.match(sData, "%b<>%s+(%S+)")
можно писать код:
Код
sData:match"%b<>%s+(%S+)"

Оптимизация состоит в использовании метода вместо функции, ну и конечно же меньше символов писать (в свою очередь интерпретатор загружает данный код чуть-чуть быстрее).

И ещё обратите внимание на отсутствие внешних скобок big_smile.gif Можно было написать так:
Код
sData:match("%b<>%s+(%S+)")
однако, в данном случае внешние скобки можно опустить)))

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

Автор: Wariner 15.1.2009, 19:39

К вопросу об оптимизации. я пишу так(когда учился где то подсмотрел bad_smile.gif ):

Код
function ChatArrival(tUser, sData)
    sData = string.sub(sData,1,-2)
    local _,_,CmdMain = string.find(sData, "%b<>%s+(%S+)")
    if CmdMain == "!startAR"  then
        ...
    end
end

Некоторые делают более хитрый код через функции.

какой самый оптимальный код для нахождения команды и последующего выполнения действий?

Автор: Setuper 15.1.2009, 21:08

Самый оптимальный:

Код
function ChatArrival(tUser, sData)
  if sData:find"%b<>%s!startAR|"  then
    ...
  end
end


Однако чаще всего используется не одна команда, поэтому так:
Код
function ChatArrival(tUser, sData)
  local CmdMain = sData:match"%b<>%s(%S+)|"
  if CmdMain then
    if CmdMain=="!startAR"  then
      ...
    elseif ...
    end
  end
end

Автор: Wariner 28.1.2009, 20:23

Есть два почти одинаковых куска кода(практически по странице каждый) отличаются только таблицами. Будет ли оптимизация если вместо них использовать функцию типо func(tTable)
?

Как часто можно использовать сборщик мусора? Вот например накидал за 5 минут лог маинчата каждое отправленное сообщение пока сохраняется сразу, при активном общении количество используемой памяти растёт в разы. Добавил сборщик мусора после каждого сохранения. Проблема решина, но целисообразно ли использовать такой код?

Автор: Setuper 28.1.2009, 21:01

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

Сборщик мусора следует использовать как можно реже, так как данная операция ресурсоёмка. По большей части мусор накапливается из-за конкатенаций, потому что при складывании строк память выделяется на каждый кусок складываемой строки (http://mydc.ru/r/?http://vkik.ru/042/6.htm)

Автор: ivan683 2.8.2010, 22:13

В lua внутри функций можно создавать новые. Причём этим функциям доступны локальные переменные создавшей.
Это позволяет легко создавать подобие класса.

Код
function new_object()
local obj = {}

local private_var = "private"

obj.public_var = "public"

function obj.public_fnc()
  return obj.public_var
end

local function private_fnc()
  return private_var
end

return obj
end


возможен даже синглтон

Код
single_object = (function()
local obj = {}

local private_var = "private"

obj.public_var = "public"

function obj.public_fnc()
  return obj.public_var
end

local function private_fnc()
  return private_var
end

return obj
end)() -- пустая пара скобок это вызов функции созданной в первой.

Автор: Setuper 23.9.2010, 10:09

Частое действие в скриптах - это проверка сообщения чата на ввод пользователем какой-либо команды.
Практически в каждом скрипте делается данная проверка.
Данное действие должно быть максимально оптимизировано.

Наиболее оптимизированный вариант:

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos):find('!', 1, true) then --поиск префикса команды

    -- дальнейшие действия (в том числе поиск и сравнение команд)

  end
end


В чём преимущество?
В таком использовании метод find работает очень эффективно, так как, во-первых, регулярные выражения отключены, во-вторых, строки сравниваются блоками в памяти (такое сравнение практически мгновенное). Представим на мгновение что поиск команд осуществляется каждый раз, когда мы пишем сообщение в чат, да ещё и не один раз, а столько раз, в скольких скриптах используются команды.

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

Применяя предложенный вариант в своих скриптах, мы по максимуму оптимизируем события чата

Автор: Nickolya 23.9.2010, 11:57

Я ошибаюсь, или оно будет искать во всем сообщении юзера восклицательный знак? Тогда срабатывания могут быть вполне частыми... Можно четко установить что никаких пробелов не должно быть перед командой и обрезать строку до одного символа, затем проверять совпадение символа с префиксом команд, так не оптимальнее?

Автор: Ksan 23.9.2010, 13:11

Цитата
'!', #tUser.sNick + 3
Проверяет непосредственно за ником только, как я понял.

Автор: Setuper 23.9.2010, 15:27

Nickolya, ты прав. Так и нужно сделать big_smile.gif
Подправил.

Автор: Alexey 24.9.2010, 4:53

Для универсальности можно ещё объявить local sPrefix = SetMan.GetString(29):sub(1,1) и использовать эту переменную.

Цитата(Setuper @ 23.9.2010, 11:09) *
Предложенный вариант поиска основан на поиске единого префикса команд, ведь команды юзаются пользователями относительно редко, а сообщения чата практически никогда не начинаются с указанного префикса.

А как-же "громкие" ники, начинающиеся со знака, совпадающего с префиксом команд? Обращение к ним будет расценено как команда и пойдёт на дальнейшую проверку. Если таких пользователей несколько и они активно общаются, то дополнительная проверка будет проходить не так уж и редко.

Автор: Setuper 24.9.2010, 9:03

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

Автор: ivan683 25.9.2011, 23:48

Цитата(Setuper @ 3.1.2009, 17:02) *
[*]Большинство команд протокола NMDC оканчивается на символ "|", который служит для разделения команд, когда они находятся в одном потоке или в одной строке. Поэтому эффективнее отсылать 1 раз единую строку с командами, нежели отсылать несколько раз отдельные команды.
Код
local t = {
  "$MyINFO $ALL nick1 $ $ $ $0$",
  "$MyINFO $ALL nick2 $ $ $ $0$",
  "$MyINFO $ALL nick3 $ $ $ $0$"
}
local sStr = ""
for i, v in ipairs(t) do
  sStr = (sStr == "" and "" or sStr.."|")..v
end
Core.SendToAll(sStr)
тоже самое касается и сообщений (если вместо команд отправляются сообщения).


Ужос какой.

Код
local t = {
  "$MyINFO $ALL nick1 $ $ $ $0$",
  "$MyINFO $ALL nick2 $ $ $ $0$",
  "$MyINFO $ALL nick3 $ $ $ $0$"
}
Core.SendToAll(table.concat(t, "|"))

Автор: MIKHAIL 2.1.2012, 3:29

В http://mydc.ru/topic1018.html?view=findpost&p=33816 часть кода:

Код
local iPos = #tUser.sNick + 4

Подскажите, пожалуйста, что означает число "4", каков его смысл?

P.S.: если некорректно задавать здесь вопрос, я перенесу его в соотв. раздел.

Автор: Enyby 2.1.2012, 11:33

#tUser.sNick - число символов в нике
4 - число символов до начала сообщения:

Код
<Ник> Сообщение|

В данном случае длина ника составляет 3 символа, а смещение 4 переходит вперед по символам "<> ", пропуская их. Таким образом это будет позиция первого символа сообщения.

ADD:
Цитата(Setuper @ 23.9.2010, 9:09) *
Наиболее оптимизированный вариант:

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos):find('!', 1, true) then --поиск префикса команды

    -- дальнейшие действия (в том числе поиск и сравнение команд)

  end
end


Есть код:
Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  local iLimit = 10000000
  local sChar = ''
  local iStart = os.time()
  for i = 0, iLimit, 1 do
    sChar = sData:sub(iPos, iPos):find('!', 1, true)
  end
  Core.SendToAll("" .. os.difftime(os.time(), iStart));
  iStart = os.time()
  for i = 0, iLimit, 1 do
    sChar = sData:sub(iPos, iPos) == '!'
  end
  Core.SendToAll("" .. os.difftime(os.time(), iStart));
end

Есть результат:
Цитата
[10:27:50] *16
[10:27:50] *8
[10:27:50] 127.0.0.1 | ?? на хабе Находится <ВВВВВВВВВВВВВВВВВВВВ> test

Есть вывод:
Прямое сравнение быстрее любых find'ов.

ADD:
Другой вопрос, если префиксов несколько. Там или циклом или регуляркой.

Автор: MIKHAIL 2.1.2012, 22:57

Как вариант – использовать модуль, который будет следить за пользователями с "нужным(ми)" префиксами, а также учитывал статистически какие команды, сообщения (правильные или неправильные) вводятся... и, используя эти данные, выбирал (переключался на) наиболее оптимальный вариант поиска для всевозможных случаев (или, по крайней мере, учесть те случаи, которые возможны при использовании тех скриптов, которые уже есть) . А данные от этого модуля могли бы использовать все скрипты.
still_dreaming.gif

Автор: Enyby 2.1.2012, 23:10

А? Ты че-то не то пишешь. Или не туда. Тут речь о префиксах команд. Т. е. о наименее ресурсоёмком определение, что именно прислал пользователь - команду или текстовое сообщение в чат.
"!regme" - это команда
"Привет всем!" - это сообщение.
В первом случае скрипт должен отработать и что-то сделать, перехватив вывод команды в чат, а во-втором, пропустить сообщение в чат, ничего не выполняя, с наименьшими затратами ресурсов.

ADD:
Продолжая тему оптимизации.

Тест
Код:
Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  local iLimit = 10000000
  local sChar = '!'
  local iChar = sChar:byte(1);
  local iCounter = 0;
  
  local iStart = os.time()
  iCounter = 0
  for i = 0, iLimit, 1 do
    if sData:sub(iPos, iPos):find('!', 1, true) then
        iCounter = iCounter + 1
    end
  end
  Core.SendToAll(iCounter .. " " .. os.difftime(os.time(), iStart));
  
  iStart = os.time()
  iCounter = 0
  for i = 0, iLimit, 1 do
    if sData:sub(iPos, iPos) == '!' then
       iCounter = iCounter + 1
    end
  end
  Core.SendToAll(iCounter .. " " .. os.difftime(os.time(), iStart));
  
  iStart = os.time()
  iCounter = 0
  for i = 0, iLimit, 1 do
    if sData:byte(iPos) == iChar then
       iCounter = iCounter + 1
    end
  end
  Core.SendToAll(iCounter .. " " .. os.difftime(os.time(), iStart));
end

Результат:
Код
[22:18:22] *10000001 19
[22:18:22] *10000001 11
[22:18:22] *10000001 10
[22:18:22] 127.0.0.1 | ??    на хабе    Находится <ВВВВВВВВВВВВВВВВВВВВ> !say test
[22:19:11] *0 18
[22:19:11] *0 11
[22:19:11] *0 9
[22:19:11] 127.0.0.1 | ??    на хабе    Находится <ВВВВВВВВВВВВВВВВВВВВ> test

Вывод:
Самый быстрый способ - использование byte, для получения кода конкретного символа.


UPD: Исправил ошибку в коде.

PS что интересно, так это четко видная буферизация вывода. В клиент все пришло одновременно, независимо от того, что отдельные строки отсылались с разрывом порядка 10 секунд.

Автор: MIKHAIL 2.1.2012, 23:11

Это с идеальной точки зрения...
Некоторые пишут:

Цитата
"Привет!"

А некоторые:
Цитата
"!Всем привет!"
"!С наступившим Новым 2012 Годом!"

И так далее... надо и такое учитывать,)
Enyby, Вы так "жёстко" определили где команда, а где сообщение. Это понятно, правильно. Но ведь есть разные случаи жизни, кто-то ошибается с вводом команды... и получается сообщение... по разным причинам. Или кто-то подбирает команды... а в результате могут получатся и команды, и сообщения.

P.S.:
Код
[22:07:31] 127.0.0.1 | ??    на хабе    Находится <ВВВВВВВВВВВВВВВВВВВВ> test

Уточните, пожалуйста, что это?

Автор: Enyby 2.1.2012, 23:29

Ну и что. На 10 млн проверок (!) нужно порядка 10 секунд, на не самой быстрой машине. Т. е. порядка 1 млн проверок в секунду. Это не существенно. У вас юзеров за спам забанит намного раньше.

ADD:

Цитата(MIKHAIL @ 2.1.2012, 22:11) *
Enyby, Вы так "жёстко" определили где команда, а где сообщение. Это понятно, правильно. Но ведь есть разные случаи жизни, кто-то ошибается с вводом команды... и получается сообщение... по разным причинам. Или кто-то подбирает команды... а в результате могут получатся и команды, и сообщения.

А так и есть. Все варианты не предугадаешь. Так что и не стоит даже заморачиваться. Завтра кошка будет по клавиатуре бегать, так вы скрипт анти кошка писать будете? big_smile.gif
Цитата(MIKHAIL @ 2.1.2012, 22:11) *
Код
[22:07:31] 127.0.0.1 | ??    на хабе    Находится <ВВВВВВВВВВВВВВВВВВВВ> test

Уточните, пожалуйста, что это?

Это вывод в ДС клиенте сообщения в чат. Временной штамп, ип, страна, локация/провайдер, ник и само сообщение.
Чтоб было понятнее - запустите тестовый скрипт и отправьте сообщение.
Правда хаб подвисает на время брожения в циклах. Так что это нормально.

ADD:
В общем стоит задача отсеять "возможно команды" от точно "не команд". Все что началось не с префикса - командой быть не может. А если есть префикс, то тогда нужно регулярку, захватывать текст и проверять что это, известная команда или отсебятина. В любом случае, это будет менее ресурсоёмко, чем проверка каждого сообщения регуляркой.

Автор: AirKobra45 14.3.2012, 19:07

И так далее... надо и такое учитывать,)

Ну так после отсеивания возможной команды следует проверка на саму команду. Так что если там и кошка пробежала то ничего страшного, выведется в чат как сообщение! А уж если команда то тогда уж и действие и return true скрипту в руки!

Всё выше описанное участниками форума делается примерно так

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos):find('!', 1, true) then --поиск префикса команды

                        -- дальнейшие действия (в том числе поиск и сравнение команд)
        local cmd = sData:sub(iPos+1,-2):match"(%S+)" --забираем то что после префикса
    if cmd == "команда" then -- Вот и само сравнение команды
      --Действие
              Core.SendToAll("<"..tUser.sNick.."> ".."Скрипт работает!") --Например так!
    return true  --Не даём команде попасть в чат.
    end
  end
end

Автор: alex82 12.3.2013, 4:53

А вот небольшое замечание, касающееся хаба PtokaX.

Как известно, скриптовые функции, отправляющие данные юзерам (Core.SendToAll, Core.SendToUser, и.т.д.) не требуют наличия в конце строки символа |, и, при необходимости, добавляют его сами. Удобно, не спорю. Но давайте заглянем в исходный код:

Код
    if(sData[szLen-1] != '|') {
        memcpy(g_sBuffer, sData, szLen);
        g_sBuffer[szLen] = '|';
        g_sBuffer[szLen+1] = '\0';
        UserSendCharDelayed(u, g_sBuffer, szLen+1);
    } else {
        UserSendCharDelayed(u, sData, szLen);
    }

На этом куске функции Core.SendToUser нам вырисовывается такая картина маслом: если функция получила строку, завершающуюся символом |, она просто отправляет данные юзеру, ну а если символа | в конце нет, то вся строка копируется в буфер, затем к ней добавляется | и завершающий нулевой байт, и только после этого она отсылается юзеру.

Вывод достаточно прост: если при отправке данных из скриптов вы будете сами добавлять | в конце, эти скрипты будут работать чуток быстрее.

Автор: mariner 12.3.2013, 7:23

Тут должен был быть пост с вопросом : "зачем пепяке с++ при таком коде?" Но его тут не будет.

Автор: Setuper 12.3.2013, 15:26

Это не единственный грабли. Если присмотреться к исходникам, то там их можно очень много найти. Я уже не говорю о "макаронности" кода.
По всей видимости у них действует правило: "Работает - не трогай!", и поэтому рефакторинг кода никогда не делается.

Автор: MIKHAIL 24.3.2013, 19:17

Прочитав критическое замечание http://mydc.ru/topic1018.html?view=findpost&p=45041 от alex82 и будучи новичком в С++, все же задам вопрос: а какие варианты решения возможны в данном случае?
Предположу, что автор Птоки таким образом обходит всевозможные варианты ошибок, связанные с некорректным завершением отсылаемых команд/сообщений, т.е. в конце всегда должен быть символ "|".
P.S.: читая со стороны одну лишь критику, для непосвящённого довольно сложно почерпнуть что-то полезное.

Автор: Setuper 24.3.2013, 21:10

проблема не в символе, а в лишнем копировании данных

не смотрел реализацию функции UserSendCharDelayed, но возможно стоило написать так:

Код
UserSendCharDelayed(u, sData, szLen);
if(sData[szLen-1] != '|') {
  UserSendCharDelayed(u, "|", 1);
}

Автор: Alexey 29.3.2013, 18:15

Цитата(alex82 @ 12.3.2013, 4:53) *
Вывод достаточно прост: если при отправке данных из скриптов вы будете сами добавлять | в конце, эти скрипты будут работать чуток быстрее.


Вот что по поводу этого совета говорит PPK:
Раскрывающийся текст
Цитата(PPK)
[05:17] <PPK> he is wrong
[05:17] <PPK> adding pipe in lua is slower that when ptokax add it laughing.gif
[05:19] <PPK> example:
sMsg = "This is a test"
slow way -> Core.SendToAll((2, sMsg.."|")
fast way -> Core.SendToAll((2, sMsg)
[15:27:31] <PPK> function OnStartup()
starttime = os.time()
Core.SendToAll("Start: "..tostring(starttime))

sMsg = "This is a test"

for i = 1, 20000000 do
Core.SendToProfile(2, sMsg.."|")
end

endtime = os.time()
Core.SendToAll("End: "..tostring(endtime))
Core.SendToAll("Diff: "..tostring(endtime-starttime))
end
[15:27:57] <PPK> x64 ptokax + 4 GB memory (result of this script is memory usage close to 4 GB)
[15:28:07] <PPK> first test was with sMsg = "This is a test|"
[15:28:19] <PPK> that should be fastest -> taken 12 second
[15:28:30] <PPK> second test was with sMsg = "This is a test"
[15:28:49] <PPK> that should be slower, but again take only 12 second
[15:29:19] <PPK> and thirt test was with sMsg = "This is a test"
and Core.SendToProfile(2, sMsg.."|") (previous two without adding pipe here)
[15:29:23] <PPK> and this one take 16 seconds
[15:29:49] <PPK> because lua is on string operation allocate new memory, copy string to that new memory and hash that string


В беседу включился sphinx и провёл свои тесты:
Раскрывающийся текст
Цитата(sphinx)
[15:51:49] <sphinx> function OnStartup()
local iStart = os.time()
sMsg = "This is a test"
for i = 1, 10000000 do
Core.SendToProfile(2, sMsg.."|")
end
Core.SendToAll( tostring (os.difftime(os.time(), iStart)))
end
[15:52:00] <sphinx> This takes 6 seconds
[15:52:22] <sphinx> And Core.SendToProfile(2, sMsg) takes only 5 big_smile.gif
[15:56:27] <sphinx> But... If I set Core.SendToProfile(2, "This is a test|") it takes 4 seconds big_smile.gif
[15:57:59] <sphinx> And in real script I do need that string operation, so you right - it will be slower to add a pipe


Если вкратце: добавлять | с помощью конкатенации — плохая идея, не стоит так делать. Если обойтись без конкатенации, то ускорение в их тестах едва заметно и результаты часто совпадают с результатами без |.

Автор: Setuper 30.3.2013, 15:36

Ну да, что в Lua идет копирование всей строки при конкатенации, что в коде - тоже копирование всей строки.
Действительно, в Lua кроме копирования при каждом создании строки ещё и рассчитывается её хеш код.

Другое дело что в хабе лишнее копирование строк можно было бы исключить, что, однако, PPK не делает big_smile.gif

Автор: Iskandark 19.10.2014, 19:07

Сообщение 23.9.2010, 11:09 от Setuper

Цитата(Setuper @ 23.9.2010, 11:09) *
Частое действие в скриптах - это проверка сообщения чата на ввод пользователем какой-либо команды.
Практически в каждом скрипте делается данная проверка.
Данное действие должно быть максимально оптимизировано.

Наиболее оптимизированный вариант:

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos):find('!', 1, true) then --поиск префикса команды

    -- дальнейшие действия (в том числе поиск и сравнение команд)

  end
end


В чём преимущество?
В таком использовании метод find работает очень эффективно, так как, во-первых, регулярные выражения отключены, во-вторых, строки сравниваются блоками в памяти (такое сравнение практически мгновенное). Представим на мгновение что поиск команд осуществляется каждый раз, когда мы пишем сообщение в чат, да ещё и не один раз, а столько раз, в скольких скриптах используются команды.

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

Применяя предложенный вариант в своих скриптах, мы по максимуму оптимизируем события чата


Возник вопрос. Использование сравнения более производительно, чем использование метода find?
какой код более производительный?

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos) == '!'  then --поиск префикса команды
    -- дальнейшие действия (в том числе поиск и сравнение команд)
  end
end


или

Код
function ChatArrival(tUser, sData)
  local iPos = #tUser.sNick + 4
  if sData:sub(iPos, iPos):find('!', 1, true) then --поиск префикса команды
    -- дальнейшие действия (в том числе поиск и сравнение команд)
  end
end

Автор: Setuper 20.10.2014, 10:24

Да, действительно, find - это лишнее, ведь у нас остался 1 символ.
Простое сравнение уместнее.