myDC.ru

Здравствуйте, гость ( Вход | Регистрация )

 
2 страниц V   1 2 >  
Ответить в данную темуНачать новую тему

> Способы Повышения Производительности, методы оптимизации lua кода

Setuper
сообщение 3.1.2009, 15:02
Сообщение #1


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




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

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

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

  • Компилятор Lua способен выполнять элементарную оптимизацию. Например, он оптимизирует простые выражения с участием переменных и констант. Не обязательно заменять, скажем, выражение
    Код
    a = 2 * 34567 + b
    на
    Код
    a = 69134 + b
    Однако в открытом lua коде эту оптимизацию всё же следует производить.
  • Скомпилированный код загружается интерпретатором в разы быстрее из-за того, что интерпретатору не надо тратить время на перевод скрипта в байт-код.



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

При разработке программ на 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"
Go to the top of the page
+Quote Post
Wariner
сообщение 3.1.2009, 18:19
Сообщение #2


Самый главный активист :-D
***********

Группа: Модераторы
Сообщений: 2 790
Регистрация: 29.6.2008
Из: г. Тула
Пользователь №: 97
Спасибо сказали: 440 раз




Сразу вопрос, интерпретатор переводит скрипт в бинарный вид один раз при загрузке или каждый раз при обращении к скрипту?
Go to the top of the page
+Quote Post
Setuper
сообщение 3.1.2009, 19:26
Сообщение #3


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




Конечно же 1 раз при загрузке скрипта. Весь загруженный бинарный код находится в ОЗУ до тех пор пока скрипт не будет остановлен или перезапущен.
Go to the top of the page
+Quote Post
Wariner
сообщение 3.1.2009, 20:35
Сообщение #4


Самый главный активист :-D
***********

Группа: Модераторы
Сообщений: 2 790
Регистрация: 29.6.2008
Из: г. Тула
Пользователь №: 97
Спасибо сказали: 440 раз




А смысл ли его тогда компилировать?! Или если скрипт скомпилированный то в памяти он хранится не будет?
Go to the top of the page
+Quote Post
Setuper
сообщение 3.1.2009, 20:46
Сообщение #5


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




я же написал чем отличается скомпилированный код от нескомпилированного.
1) Выполняется простейшая оптимизация.
2) Скомпилированный код быстрее загружается в память.
Go to the top of the page
+Quote Post
Setuper
сообщение 11.1.2009, 19:43
Сообщение #6


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




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

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

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

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


Спасибо сказали:
Go to the top of the page
+Quote Post
Wariner
сообщение 15.1.2009, 19:39
Сообщение #7


Самый главный активист :-D
***********

Группа: Модераторы
Сообщений: 2 790
Регистрация: 29.6.2008
Из: г. Тула
Пользователь №: 97
Спасибо сказали: 440 раз




К вопросу об оптимизации. я пишу так(когда учился где то подсмотрел 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

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

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


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 15.1.2009, 21:08
Сообщение #8


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




Самый оптимальный:
Код
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


Спасибо сказали:
Go to the top of the page
+Quote Post
Wariner
сообщение 28.1.2009, 20:23
Сообщение #9


Самый главный активист :-D
***********

Группа: Модераторы
Сообщений: 2 790
Регистрация: 29.6.2008
Из: г. Тула
Пользователь №: 97
Спасибо сказали: 440 раз




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

Как часто можно использовать сборщик мусора? Вот например накидал за 5 минут лог маинчата каждое отправленное сообщение пока сохраняется сразу, при активном общении количество используемой памяти растёт в разы. Добавил сборщик мусора после каждого сохранения. Проблема решина, но целисообразно ли использовать такой код?
Go to the top of the page
+Quote Post
Setuper
сообщение 28.1.2009, 21:01
Сообщение #10


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




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

Сборщик мусора следует использовать как можно реже, так как данная операция ресурсоёмка. По большей части мусор накапливается из-за конкатенаций, потому что при складывании строк память выделяется на каждый кусок складываемой строки (http://vkik.ru/042/6.htm)
Go to the top of the page
+Quote Post
ivan683
сообщение 2.8.2010, 22:13
Сообщение #11


Начинающий
*

Группа: Пользователи
Сообщений: 12
Регистрация: 15.7.2009
Пользователь №: 3 884
Спасибо сказали: 8 раз




В 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)() -- пустая пара скобок это вызов функции созданной в первой.


Спасибо сказали:
Go to the top of the page
+Quote Post
Setuper
сообщение 23.9.2010, 10:09
Сообщение #12


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




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

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

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

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

  end
end


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

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

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


Спасибо сказали:
Go to the top of the page
+Quote Post
Nickolya
сообщение 23.9.2010, 11:57
Сообщение #13


Главный ра******й тут...
*********

Группа: Главные администраторы
Сообщений: 1 727
Регистрация: 18.5.2008
Из: RF, 2la
Пользователь №: 1
Спасибо сказали: 776 раз




Я ошибаюсь, или оно будет искать во всем сообщении юзера восклицательный знак? Тогда срабатывания могут быть вполне частыми... Можно четко установить что никаких пробелов не должно быть перед командой и обрезать строку до одного символа, затем проверять совпадение символа с префиксом команд, так не оптимальнее?
Go to the top of the page
+Quote Post
Ksan
сообщение 23.9.2010, 13:11
Сообщение #14


Белый Волк
*********

Группа: Пользователи
Сообщений: 1 723
Регистрация: 11.9.2008
Из: г.Томск
Пользователь №: 516
Спасибо сказали: 657 раз




Цитата
'!', #tUser.sNick + 3
Проверяет непосредственно за ником только, как я понял.
Go to the top of the page
+Quote Post
Setuper
сообщение 23.9.2010, 15:27
Сообщение #15


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




Nickolya, ты прав. Так и нужно сделать big_smile.gif
Подправил.
Go to the top of the page
+Quote Post
Alexey
сообщение 24.9.2010, 4:53
Сообщение #16


7 квадратиков
*******

Группа: Модераторы
Сообщений: 793
Регистрация: 21.1.2009
Пользователь №: 1 895
Спасибо сказали: 301 раз




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

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

А как-же "громкие" ники, начинающиеся со знака, совпадающего с префиксом команд? Обращение к ним будет расценено как команда и пойдёт на дальнейшую проверку. Если таких пользователей несколько и они активно общаются, то дополнительная проверка будет проходить не так уж и редко.
Go to the top of the page
+Quote Post
Setuper
сообщение 24.9.2010, 9:03
Сообщение #17


RusHub team lead
**************

Группа: Модераторы
Сообщений: 4 030
Регистрация: 20.6.2008
Из: г. Королёв (Моск. обл.)
Пользователь №: 46
Спасибо сказали: 1708 раз




Ну это так должно совпасть, чтобы ники начинались с префикса, да ещё и активно общались на хабе.
Это редкое явления. А оптимизация рассчитана на среднестатистический хаб.
Потом, ну и что что ники начинаются с префикса, ведь как бы скрипт не был написан, он будет проверять возможные команды, а предложенный вариант написания более оптимизирован для большинства случаев.
Go to the top of the page
+Quote Post
ivan683
сообщение 25.9.2011, 23:48
Сообщение #18


Начинающий
*

Группа: Пользователи
Сообщений: 12
Регистрация: 15.7.2009
Пользователь №: 3 884
Спасибо сказали: 8 раз




Цитата(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, "|"))


Спасибо сказали:
Go to the top of the page
+Quote Post
MIKHAIL
сообщение 2.1.2012, 3:29
Сообщение #19


KEEP CLEAR AT ALL TIMES
****

Группа: Пользователи
Сообщений: 141
Регистрация: 4.9.2011
Из: Беларусь, Минск
Пользователь №: 9 667
Спасибо сказали: 3 раза




В Сообщение #12 часть кода:
Код
local iPos = #tUser.sNick + 4

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

P.S.: если некорректно задавать здесь вопрос, я перенесу его в соотв. раздел.
Go to the top of the page
+Quote Post
Enyby
сообщение 2.1.2012, 11:33
Сообщение #20


Освоившийся участник
*****

Группа: Пользователи
Сообщений: 391
Регистрация: 4.11.2009
Из: Дом
Пользователь №: 4 923
Спасибо сказали: 239 раз




#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:
Другой вопрос, если префиксов несколько. Там или циклом или регуляркой.


Спасибо сказали:
Go to the top of the page
+Quote Post

2 страниц V   1 2 >
Ответить в данную темуНачать новую тему
2 чел. читают эту тему (гостей: 2, скрытых пользователей: 0)
Пользователей: 0

Collapse

> Похожие темы

  Тема Ответов Автор Просмотров Последнее сообщение
No New Posts От: Способы Повышения Производительности
От темы с ID: 1018
2 Setuper 6 276 2.2.2009, 3:13 Посл. сообщение: Setuper

 



RSS Сейчас: 28.3.2024, 14:05