Alex Nekipelov Blog

вторник, 10 января 2012 г.

ресурс dev.wikitt.com

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

Также я давно задумывался о том, что пусть и для начинающих написано много, однако все ли написанное стоит читать? Частенько для начинающих пишут такие же начинающие, поэтому с ошибками. Да и более опытные программисты тоже могут ошибаться. Также написанное где-то в блоге некоторое время назад может перестать быть актуальным на сегодняшний день. Ведь меняется язык, меняются процессоры и операционные системы… В общем пришел к тому, что хотелось бы ресурс, подобный wikipedia, но для программистов. Ну и благодаря наличию свободного времени в эти праздники, сделал сайтик http://dev.wikitt.com

Вот, что есть на данный момент:

Все желающие поделиться полезной информацией, приглашаются :-)

суббота, 12 ноября 2011 г.

Qt, resize и нехватка памяти

Недавно обнаружил в Qt интересный момент. Как известно, исключения в ней не используются. Они объясняют это ростом размера библиотеки, снижением скорости и не совместимостью с главным циклом приложения. Хорошо это или плохо, не важно. Важно то, что с этой библиотекой приходится работать. И еще важно, что исключительные ситуации бывают, без них не обойтись.

Так вот, я как то не задумывался о проблеме исключений в Qt. Но недавно наткнулся на проблему: программа гадила в память и валилась. Гадила она в память, в которую гадить вроде бы не должна:


QVector<char> buff;
buff.resize( buffSize );
memcpy( src, buff.data(), buffSize );

Размер buffSize что-то около 200 Мб. Вроде бы и не много. Но когда программа работает долгое время, память фрагментируется, одного куска размером 200 Мб может и не быть. По аналогии с STL я привык, что это корректный код. При нехватке памяти будет сгенерировано исключение, которое будет поймано и обработано, или обработчик этой проблемы освободит память. И когда вылезла беда, стал разбираться… Для выделения памяти в Qt используется аналогичные ANSI-C фукнциям qMalloc, qRealloc, qFree. При невозможности выделить память, они возвращают NULL. Только вот resize не возвращает результата, и исключение не генерируется. Получается, что указанный выше код корректен в STL, но не корректен в случае контейнеров Qt. Подобная же проблема имеется и с классом QByteArray, и вероятно со многими другими.

А самое смешное, вот что говорят в рассылке на эту тему:

A short and simple question.

How an application can detect failure in QByteArray::resize(int newSize) method, since resize() is a void function?

You can’t.

If memory allocation fails, you’re out of luck. You should crash your application at this point and help the system free up memory for other applications.

Источник: http://lists.trolltech.com/qt-interest/2008-10/thread00114-0.html

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

пятница, 11 ноября 2011 г.

Сравнение http серверов приложений

Продолжу тему http серверов. На этот раз про сервера приложений для ruby и python. С другими языками я практически не дружу (не считая c++, конечно же).

Их много, все они разные, и главное отличие - скорость обработки запросов. Еще, конечно же, важно назначение http сервера. К примеру назначение http сервера Mongrel и Thin - запуск приложений, написанных на языке ruby, а Tornado или Twisted для приложений, написанных на python. Конечно их можно использовать и для раздачи статических файлов... но я не советую :-)

Из-за отсутствия нормальной реализации многопоточности в этих языках (и наличия GIL), http сервера приложений способны обслуживать клиентов только в один поток. Поэтому, чтобы заставить их использовать несколько процессоров, запускают несколько экземпляров http серверов, перед ними выставляя балансировщик (apache, nginx, lighttpd).

Основная задача, которую я хочу выполнить: узнать максимальное количество запросов в секунду, на которые способны указанные сервера приложений. Они были выбраны как самые быстрые (не считая Mongrel, конечно же). Если вы знаете более быстрые сервера приложений для ruby или python, пожалуйста, сообщите мне об этом. Методика тестирования заключается в создании минимального приложения, которое будет отдавать строку "Hello World". Сначала я хотел делать второй этап теста и отдавать больший объем данных, но судя по результатам, это не значительно влияет на результаты, поэтому их не привожу. Тестовый стенд: AMD Phenom(tm) II X4 940, 8 Gb RAM, Debian Squeeze, сборка для AMD64. Все тексты запускались несколько раз, чтобы убедится в стабильности результатов. Для теста используется ab, первый тест выполняется с аргументом -c 1, второй -c 100 (это количество параллельных запросов). При запуске я привязываю приложение к определенному процессору, чтобы оно не скакало с одного на другой. Полный лог вывода к сожалению не помещается (видимо на Blogger имеется ограничение на размер поста), исходные тексты тестов можно найти в конце текста.

Для начала о сервере Mongrel: http://ru.wikipedia.org/wiki/Mongrel. Помимо написанного в википедии добавлю, что он практически полностью написан на ruby, за исключением парсера http запросов (эта часть написана на C). Как уже указано в википедии, до недавнего времени достаточно нагруженный проект Twitter использовал Mongrel. Вот из статейки об архитектуре Twitter http://highscalability.com/scaling-twitter-making-twitter-10000-percent-faster:

Mongrel - hybrid Ruby/C HTTP server designed to be small, fast, and secure

На тот момент twitter использовали примерно 350 000 пользователей, уже кучу серверов, и Mongrel справлялся со своей задачей. Надо отметить, что во время второго теста с 100 клиентов, время от времени ab жаловался на "apr_socket_recv: Connection timed out (110)", что не очень хорошо. Вот, на что он способен (напоминаю, что смотреть нужно значение "Requests per second"):


Concurrency Level:      1
Time taken for tests:   36.468 seconds
Requests per second:    2742.11 [#/sec] (mean)
Time per request:       0.365 [ms] (mean)
Time per request:       0.365 [ms] (mean, across all concurrent requests)
Transfer rate:          353.47 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   26.562 seconds
Requests per second:    3764.78 [#/sec] (mean)
Time per request:       26.562 [ms] (mean)
Time per request:       0.266 [ms] (mean, across all concurrent requests)
Transfer rate:          485.30 [Kbytes/sec] received

Теперь посмотрим на Thin. Он использует EventMachine, который частично написан на C++. Соответственно используется асинхронная обработка запросов. Парсер запросов также написан на C. Каких-либо историй успехов найти не смог (вероятно плохо искал). Вот его результаты:


Concurrency Level:      1
Time taken for tests:   34.807 seconds
Requests per second:    2872.98 [#/sec] (mean)
Time per request:       0.348 [ms] (mean)
Time per request:       0.348 [ms] (mean, across all concurrent requests)
Transfer rate:          412.43 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   16.325 seconds
Requests per second:    6125.52 [#/sec] (mean)
Time per request:       16.325 [ms] (mean)
Time per request:       0.163 [ms] (mean, across all concurrent requests)
Transfer rate:          879.35 [Kbytes/sec] received

Thin явно быстрее Mongrel при увеличении количества параллельных запросов. Основное преимущество Thin в том, что его можно использовать для реализации Comet сервера.

Теперь перейдем к python. Честно говоря, я и с этим языком практически не дружу. Поэтому даже не знаю, кто в стане python является самым быстрым. Наиболее часто я слышу про Tornado и Twisted. Для начала о Twisted. Большей частью написан на python, но местами, разумеется, на C. Является не сервером приложений, а событийно-ориентированным фреймворком для разработки серверов. Т.е. то, чем является EventMachine для ruby. Сразу замечу, что результаты не впечатляют. Возможно потому, что парсер запросов не написан на C :-) Во время тестирования ошибка "apr_socket_recv: Connection refused (111)" выскакивала при отсутствии параллельных запросов.


Concurrency Level:      1
Time taken for tests:   70.951 seconds
Requests per second:    1409.43 [#/sec] (mean)
Time per request:       0.710 [ms] (mean)
Time per request:       0.710 [ms] (mean, across all concurrent requests)
Transfer rate:          202.33 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   31.263 seconds
Requests per second:    3198.64 [#/sec] (mean)
Time per request:       31.263 [ms] (mean)
Time per request:       0.313 [ms] (mean, across all concurrent requests)
Transfer rate:          459.18 [Kbytes/sec] received

Теперь Tornado. Был создан в компании Friendfeed и предоставлен на суд общественности после приобретения последней компанией Facebook. Они написали Tornado как раз потому, что Twisted имеет некоторые проблемы с быстродействием, источник: http://bret.appspot.com/entry/tornado-web-server. Судя по результатам, у них это получилось.


Concurrency Level:      1
Time taken for tests:   50.786 seconds
Requests per second:    1969.03 [#/sec] (mean)
Time per request:       0.508 [ms] (mean)
Time per request:       0.508 [ms] (mean, across all concurrent requests)
Transfer rate:          334.58 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   19.708 seconds
Requests per second:    5074.07 [#/sec] (mean)
Time per request:       19.708 [ms] (mean)
Time per request:       0.197 [ms] (mean, across all concurrent requests)
Transfer rate:          862.20 [Kbytes/sec] received

Признаться, я приятно удивлен тем, что ruby сервера справляются лучше, чем python. Но возможно причина в том, что разбор запросов в случае ruby осуществляется на C. Я не ставил целью сравнивать между собой эти два прекрасных языка программирования. Моя цель в том, чтобы показать общую картину. Хотя для того, чтобы показать ее полностью, следовало сравнить фреймворки Ruby On Rails и Django. Там все будет значительно худе, в лучшем случае несколько сотен запросов в секунду. Вот презентация: http://www.scribd.com/doc/51076611/RubyConfUA-Ruby-on-Rails-1000-запросов-в-секунду-Макс-Лашин, где описывается ситуация, что для обработки 1000 запросов в секунду требуется 14 серверов. К сожалению нет деталей, сколько из этих серверов отданы для БД, а сколько для Ruby On Rails. Разумеется это уже не "Hello, world", и вероятно основная нагрузка будет идти на БД. Однако, если судить по архитектуре других проектов, серверов слишком много для такой нагрузки.

Мне кажется, что подобные языки не предназначены для разработки http серверов, где приходится обслуживать большое количество клиентов. Я пишу именно http серверов, а не http приложений. В любое приложение на C или C++ легко встраивается почти любой язык программирования. И явно такие сервера должны быть. Интересно, почему они не пользуются популярностью? Для сравнения, вот, на что способен nginx, работая одним воркером:


Concurrency Level:      1
Time taken for tests:   17.761 seconds
Requests per second:    5630.18 [#/sec] (mean)
Time per request:       0.178 [ms] (mean)
Time per request:       0.178 [ms] (mean, across all concurrent requests)
Transfer rate:          1264.59 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   3.889 seconds
Requests per second:    25715.85 [#/sec] (mean)
Time per request:       3.889 [ms] (mean)
Time per request:       0.039 [ms] (mean, across all concurrent requests)
Transfer rate:          5776.02 [Kbytes/sec] received

При этом у nginx задача чуть сложнее, я не стал писать к нему модуль, поэтому nginx отдает файл. Результаты для 100 клиентов впечатляют. Я несколько раз перепроверял, но ошибки нет. Хотя если вдуматься, что такое 100 000 раз открыть и отправить какой-то там файлик? Это займет пол секунды (специально проверил). Основной расход ресурсов уходит на коммуникацию. Раз уж перешел к отдаче файлов, в последнее время часто упоминается yaws, написанный на Erlang. Встретил даже вот такие восхищенные строки:

Yaws (Yet Another Web Server) - web сервер написанный на языке Erlang. На языке, который по праву считается крайне производительным. Тоже самое можно сказать и об самом web сервере: по сравнению с apache2, yaws - просто реактивный.
http://the-bosha.ru/2010/08/30/yaws-lyogkiy-web-server-na-erlang/

Ну и множество других восторженных отзывов. Пройти мимо просто нельзя. Ради этого даже поставил yaws из репозитария wheezy, чтобы тестировать последнюю версию. Вот, что я получил:


Server Software:        Yaws
Concurrency Level:      1
Time taken for tests:   33.808 seconds
Requests per second:    2957.89 [#/sec] (mean)
Time per request:       0.338 [ms] (mean)
Time per request:       0.338 [ms] (mean, across all concurrent requests)
Transfer rate:          652.82 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   24.427 seconds
Failed requests:        171
   (Connect: 0, Receive: 57, Length: 57, Exceptions: 57)
Write errors:           0
Total transferred:      22587118 bytes
HTML transferred:       1998860 bytes
Requests per second:    4093.88 [#/sec] (mean)
Time per request:       24.427 [ms] (mean)
Time per request:       0.244 [ms] (mean, across all concurrent requests)
Transfer rate:          903.02 [Kbytes/sec] received

В глаза бросается строка (Connect: 0, Receive: 57, Length: 57, Exceptions: 57). И сколько бы я не запускал ab, ошибки вылезают. Видимо 100 запросов на один сервер, многовато. На реактивный он явно не похож.

UPDATE

По совету Viacheslav Biriukov повторил тест на gunicorn для python. Разбираться, как он реализован, пока нет времени. Вот его результаты:


Concurrency Level:      1
Time taken for tests:   37.928 seconds
Requests per second:    2636.57 [#/sec] (mean)
Time per request:       0.379 [ms] (mean)
Time per request:       0.379 [ms] (mean, across all concurrent requests)
Transfer rate:          424.84 [Kbytes/sec] received

Concurrency Level:      100
Time taken for tests:   19.764 seconds
Requests per second:    5059.64 [#/sec] (mean)
Time per request:       19.764 [ms] (mean)
Time per request:       0.198 [ms] (mean, across all concurrent requests)
Transfer rate:          815.27 [Kbytes/sec] received

При отсутствии конкуренции он показал себя значительно лучше Tornado и Twisted.

Итоговый результат в виде картинки:

Ну а теперь исходные тексты.

Mongrel:

require 'mongrel'

class HelloHandler < Mongrel::HttpHandler
        def process(request, response)
                response.start(200) do |head,out|
                        head['content-type'] = 'text/html'
                        out << '<p>Hello World!</p>'
                end
        end
end

h = Mongrel::HttpServer.new("0.0.0.0", "3000")
h.register("/hello", HelloHandler.new)
Thin

require 'thin'

app = proc do |env|
# Response body has to respond to each and yield strings
# See Rack specs for more info: http://rack.rubyforge.org/doc/files/SPEC.html
body = ['<p><p>Hello World!</p>']

[
    200,                                        # Status code
    { 'Content-Type' => 'text/html' },          # Reponse headers
    body                                        # Body of the response
]
end

run app
Twisted

# -*- coding: utf-8 -*-
from twisted.web import server, resource
from twisted.internet import reactor

class Simple(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return "<p>Hello world!</p>"

site = server.Site(Simple())
reactor.listenTCP(3000, site)
reactor.run()
Tornado

# -*- coding: utf-8 -*-
import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
        def get(self):
                self.write("<p>Hello, world<p>")

if __name__ == "__main__":
        application = tornado.web.Application([
                (r"/hello", MainHandler),
        ])
        http_server = tornado.httpserver.HTTPServer(application)
        http_server.listen(3000)
        tornado.ioloop.IOLoop.instance().start()
gunicorn

# -*- coding: utf-8 -
from wsgiref.validate import validator

#@validator
def app(environ, start_response):
    """Simplest possible application object"""
    data = '<p>Hello, World</p>'
    status = '200 OK'
    response_headers = [
        ('Content-type','text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

среда, 23 марта 2011 г.

Зависание при переключении раскладки

Об этой проблеме я хотел написать еще год назад, да что-то так и не написал. Совсем не до этого было.

Началось все с того, что коллега начал жаловаться, после моих изменений у него начала зависать программа при переключении раскладки. Зависание было иногда и не на долго. Потом появился еще один с такой же жалобой. Ни одна попытка воспроизвести не увенчалась успехом. Снятие дампа, трассировка, тоже не помогли. Через пол года этих "жалобщиков" стало еще больше и деваться стало некуда, пришлось разбираться. Порывшись в интернете я выяснил, что подобная проблема возникает в программах Miranda, Punto Switcher и других. И возникает она после установки Internet Explorer 7.0, который несет в себе так называемые "Дополнительные текстовые службы". После их установки зависание и начинается. А после отключения прекращается. Однако после отключения их любимый IE сам начинает вести себя не адекватно, или перестает переключать раскладку. В общем это не вариант. Еще один вариант исправления - это убрать индикатор раскладки. Но клиентам ни один из вариантов не подойдет.

Пришлось мне сделать очередной spanshot в виртуальной машине и ставить этот самый IE7. Spanshot разумеется для того, чтобы после исправления проблемы вернуть "любимый" IE6.

После долгой и нудной отладки выяснилось, что зависание происходит только при запуске определенного потока. После еще более нудной отладки выяснилось, что виновато скрытое окно, которое создается в этом потоке. Точнее проблема проявляется только после создания этого окна, именно это и было мое изменение. Но окно нужно для того, чтобы работали таймеры. В конце концов нашел гадкое место, это вызов Sleep. Это сейчас я нашел в MSDN вот такие строки:

Code that directly or indirectly creates windows (for example, DDE and COM CoInitialize). If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. If you have a thread that uses Sleep with infinite delay, the system will deadlock.

Еще пару лет назад про окна ничего не было написано. Хотя может быть я смотрел устаревший MSDN...

В общем, будьте осторожны. Если параллельный поток создает окно, очень нежелательно использовать в нем вызовы, блокирующие обработку сообщений. Иначе, в случае рассылки сообщения всем окнам, ваше приложение будет "подвисать".

суббота, 19 марта 2011 г.

ReadFile/WriteFile ERROR_INVALID_ARGUMENT

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

Как это часто бывает, корректность записи и чтения никто не проверял. Вставив соответствующие проверки я увидел, что ни запись, ни чтение не осуществляются вообще. GetLastError стабильно возвращает код ошибки 87: ERROR_INVALID_ARGUMENT или "Параметр задан неверно". А все что прочиталось - жалкие остатки из буфера для записи. При том, что этот код точно работал и никто его не менял. И ограничение на размер буфера для функций ReadFile и WriteFile составляет 32 мегабайта.

После некоторых разбирательств выяснилось, что проблема проявляется только при записи на сетевой диск, точнее Shared Folder сделанные с помощью VirtualBox.

В общем, на Shared Folder, сделанный с помощью VirtualBox нельзя выполнять вызовы WinAPI ReadFile и WriteFile передавая буфер больше 16 мегабайт за раз.