Путем раскуривания манов на офсайте Скайпа было найдено следующее решение. Тупо открываем терминал и копипастим туда следующую команду:
Автор: Роман Дмитриевич
Путем раскуривания манов на офсайте Скайпа было найдено следующее решение. Тупо открываем терминал и копипастим туда следующую команду:
Автор: Роман Дмитриевич
Пост чисто философский
Периодическое чтение кусков кода, написанных при обострении хаскеля головного мозга, выработало у меня четкую ассоциацию: функциональный стиль — это нечитаемо. Точнее стиль с множеством map/filter/zip. Вот немного облагороженный пример такого кода (автор считает, что с кодом все ок):
some_res = ", ".join(
map(str,
filter(None,
map(lambda x: getattr(obj.zip, x, None),
['a', 'b', 'c', 'd']))))
some_res = ", ".join(
map(str,
filter(None,
map(lambda x: getattr(obj.zip, x, None),
['a', 'b', 'c', 'd']))))
Без переписывания в многострочный вариант ориентироваться в нем вообще сложно:
attrs = ['a', 'b', 'c', 'd']
attr_vals = map(lambda x: getattr(obj.zip, x, None), attrs)
non_none_attrs = filter(None, attr_vals)
some_res = ", ".join(map(str, non_none_attrs))
attrs = ['a', 'b', 'c', 'd']
attr_vals = map(lambda x: getattr(obj.zip, x, None), attrs)
non_none_attrs = filter(None, attr_vals)
some_res = ", ".join(map(str, non_none_attrs))
Этот вариант читается лучше уже потому, что понизилась концентрация логики, и добавились переменные, имена которых служат документацией промежуточным результатам. Но, IMO, это не главные причины.
Первом меня на эту мысль натолкнуло наблюдение за девушкой, которая только начал учить питон и не программировала серьезно до этого. У нее вызывало затруднение определить какие параметры относятся к какой функции даже в достаточно простых выражениях, например:
x = some_func1(a, b, some_func2(c, d), e)
x = some_func1(a, b, some_func2(c, d), e)
Понятно, что со временем это прошло, но осадок остался — в выражения где много вложенных вызовов и скобок сложно быстро соотнести параметры, функции и удерживать это в голове, пока его анализируешь. Если код не форматирован построчно, как пример выше, то совсем тяжело.
Следующий случай — это функциональный стиль в скале. Его чтение у меня не вызывает того чувства трясины, какое вызывал аналогичный код в python/haskell. Тот же пример на 'скалапитоне' выглядел бы так:
some_res = ['a', 'b', 'c', 'd'].map(getattr(obj.zip, _, None))
.filter(Non
e).map(str).join(",")
some_res = ['a', 'b', 'c', 'd'].map(getattr(obj.zip, _, None))
.filter(None).map(str).join(",")
Если отвлечься от более удобной формы записи лямбды, то он все равно читается гораздо проще. Мне кажется дело в том, что он читается линейно слева направо, а не «вообще изнутри наружу, но местами слева направо», как читается код в питоне.
Это относится скорее не к функциональному стилю, а к процедурный vs ООП, но именно функциональный стиль провоцирует избавление от переменных и написание множества вложенных вызовов функций. Он как лакмусовая бумажка вскрывает плохую масштабируемость читаемости процедурных выражений:
a(x, b(c(), 1, 4), 'd')
# vs
c().b(1, 4).a(x, 'd')
a(x, b(c(), 1, 4), 'd')
# vs
c().b(1, 4).a(x, 'd')
К сожалению питон чаще всего не позволяет писать сцепленными методами, поскольку бОльщая часть методов возвращает None вместо self (а именно все, которые модифицируют объект на месте), а map/filter — функции, а не методы.
Итого я для себя сменил идею с «функциональный код нечитаем» на «функциональный код, написанный в процедурном стиле, нечитаем».
Ссылки:
ananthakumaran.in/2010/03/29/scala-underscore-magic.html
Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.
Автор: konstantin danilov
Начнем издалека — создание объекта инстанцированием класса плохо совместимо с идеями ООП. Они гласят, что код должен зависеть от интерфейсов, а не от реализаций. До тех пор пока на вход нашему коду приходят готовые объекты — все хорошо. Он будет с готовностью принимать любые типы, реализующие требуемый интерфейс, но как только мы начинаем создавать новые объекты ситуация меняется. Теперь код зависит от конкретного класса, что усложняет следующие задачи:
class A(object):
def __init__(self, val):
self.val = val
def __add__(self, val):
return A(self.val + val)
class B(A):
pass
print B(1) + 1 # <__main__.A object at 0x18877d0>
class A(object):
def __init__(self, val):
self.val = val
def __add__(self, val):
return A(self.val + val)
class B(A):
pass
print B(1) + 1 # <__main__.A object at 0x18877d0>
А хотелось бы получить экземпляр В.
Все эти проблемы связаны общей причиной — код делает работу, которую он делать не должен — инстанцирование конкретных классов. На самом деле чаще всего нам не нужен фиксированный класс. Нам нужен класс, предоставляющий определенный интерфейс. Нужно отвязаться от явного указания класса и передать его создание стороннему коду. Фактически мы хотим «виртуализовать» инстанцирование.
В самом простом случае можно воспользоваться фабричной функцией(ФФ). Если же мы хотим конфигурировать поведение ФФ, или сохранять состояние между вызовами (синглетон, пул объектов, etc), то логично сделать ФФ методом класса, в экземпляре которого будут храниться настройки. Такой класс может быть синглетоном(если конфигурация глобальная), или передаваться образом по цепочке вызовов во все точки, где нужно инстанцирование. Этот класс как раз и называется Inversion of Control Container (ICC дальше).
Для его использования нужно заменить прямое инстанцирование классов на вызов метода ICC. Параметрами метода будут требуемый интерфейс, и, возможно, контекст вызова и часть параметров для конструктора (последнее применяется редко). ICC возвращает готовый экземпляр. Конкретный класс для инстанцирования и параметры конструктора настраиваются програмно или берутся из конфигурационного файла.
Типичный пример — создание виртуальной машины в libvirt. Основная функция API принимает xml строку, описывающую виртуальную машину. Эта строка чаще всего берется вызывающим кодом из внешнего источника, потому как в большинстве случаев ему не важны подробности конфигурации для работы с VM соответственно и код создания можно унифицировать, а строку с конфигурацией использовать как черный ящик.
ICC также можно рассматривать как шаблон проектирования, объединяющий и унифицирующий другие порождающие шаблоны — ФФ, синглетон, и прочее.
Java и C# имеет различные реализации ICC (java spring, dagger) которые используются очень широко. Для питона же они практически не применяются. Сначала я покажу как написать pythonic ICC, а потом рассмотрю почему он не нужен. Написание своего связанно с тем, что по уже готовые пишутся людьми только что пришедшими с Java/C# и не отличаются питонистичностью.
Итак что можно хотеть от идеального ICC? Во-первых оставить пользовательский код почти без изменений. Во-вторых поддерживать возможность возвращать при инстанцировании целевого класса экземпляры другого класса, или определенный объект или результат вызова некоторой функции.
Итак был такой код:
class Bee(object):
def __init__(self, x):
pass
class Cee(object):
def __init__(self, x):
pass
assert isinstance(Bee(1), Bee)
class Bee(object):
def __init__(self, x):
pass
class Cee(object):
def __init__(self, x):
pass
assert isinstance(Bee(1), Bee)
Мы хотим иметь возможность не меняя код инстанцирования Bee выбирать что именно будет получаться — экземпляр Bee или Cee. С позиции duck typing классы Bee и Cee реализуют один и тот-же интерфейс и взаимозаменяемы, хоть мы это и не декларируем явным наследованием.
В принципе инстанцирование можно и не менять, но тогда его поведение будет не совсем очевидным. С первого взгляда кажется, что мы инстанцируем обычный класс Bee, а в итоге получаем экземпляр другого класса, который к классу Bee никакого отношения не имеет. Т.е. isinstance(Bee(), Bee) == False. Поэтому немного изменим пример. Bee и Cee будут наследовать общий интерфейс IBee и именно этот интерфейс мы и будем инстанцировать.
class IBee(IOCInterface):
def __init__(self, x):
pass
class Bee(IBee):
def __init__(self, x):
print "Bee.__init__ called"
class Cee(IBee):
def __init__(self, x):
print "Cee.__init__ called"
IBee.register(Bee)
assert isinstance(IBee(1), Bee)
IBee.register(Cee)
assert isinstance(IBee(1), Cee)
class IBee(IOCInterface):
def __init__(self, x):
pass
class Bee(IBee):
def __init__(self, x):
print "Bee.__init__ called"
class Cee(IBee):
def __init__(self, x):
print "Cee.__init__ called"
IBee.register(Bee)
assert isinstance(IBee(1), Bee)
IBee.register(Cee)
assert isinstance(IBee(1), Cee)
Что бы это работало нужно перехватить конструирование объекта типа IBee и вернуть что-мы-там-хотим. Для этого вспоминаем, что конструирование объекта в python выражается следующим псевдокодом:
# obj = Cls(x, y) ==>
obj = Cls.__new__(Cls, x, y)
if isinstance(obj, Cls):
Cls.__init__(obj, x, y)
# obj = Cls(x, y) ==>
obj = Cls.__new__(Cls, x, y)
if isinstance(obj, Cls):
Cls.__init__(obj, x, y)
Т.е. Cls.__new__ возвращает пустой экземпляр типа Cls, Cls.__init__ наполняет его реальными данными. Очень похоже на operator new + конструктор в С++. Итак нам нужно перегрузить IBee.__new__ и возвращать из него наш объект.
ioc = {}
class IOCInterface(object):
def __new__(cls, *args, **kwargs):
return ioc[cls](cls, *args, **kwargs)
@classmethod
def register(cls, impl):
factory = lambda ccls, *args, **kwargs:
super(IOCInterface, ccls).__new__(impl, *args, **kwargs)
cls.register_factory(factory)
@classmethod
def register_instance(cls, obj):
cls.register_factory(lambda *args, **kwargs: obj)
@classmethod
def register_factory(cls, func):
ioc[cls] = func
ioc = {}
class IOCInterface(object):
def __new__(cls, *args, **kwargs):
return ioc[cls](cls, *args, **kwargs)
@classmethod
def register(cls, impl):
factory = lambda ccls, *args, **kwargs:
super(IOCInterface, ccls).__new__(impl, *args, **kwargs)
cls.register_factory(factory)
@classmethod
def register_instance(cls, obj):
cls.register_factory(lambda *args, **kwargs: obj)
@classmethod
def register_factory(cls, func):
ioc[cls] = funcНемного пояснений. Класс IOCInterface будет базовым для всех интерфейсов. Переменная ioc будет хранить текущую конфигурацию — отображение интерфейса на фабричную функцию для этого интерфейса. Для простоты примера мы будем хранить конфигурацию в глобальной переменной. Перегруженный метод __new__ получает инстанцируемый класс первым параметром, а дальше идут параметры конструктора. Он берет зарегистрированную для этого класса фабричную функцию и создает новый объект с ее помощью. IOCInterface.register позволяет зарегистрировать класс для данного интерфейса. IOCInterface.register_instance — зарегистрировать синглетон. Для унификации они создают специальные фабричные функции.
Замечания:
делать что-то вида;
def __new__(cls, *args, **kwargs):
obj = ioc[cls]
if isinstance(obj, type):
return obj(cls, *args, **kwargs)
elif type(obj, (types.FunctionType, types.LambdaType)):
return obj(cls, *args, **kwargs)
else:
return obj
def __new__(cls, *args, **kwargs):
obj = ioc[cls]
if isinstance(obj, type):
return obj(cls, *args, **kwargs)
elif type(obj, (types.FunctionType, types.LambdaType)):
return obj(cls, *args, **kwargs)
else:
return obj
Делать этого не стоит, хотя бы потому что так мы не сможем зареги
После вышеперечисленных манипуляций все встает на свои места и пашет как надо.Это видно по выводу ifconfig:electrichp@electrichp-Lenovo-G500 ~/Загрузки/compat-drivers-2013-03-26-u $ ifconfig
eth0 Link encap:Ethernet HWaddr 20:89:84:f6:ba:25
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:16
lo Link encap:Локальная петля (Loopback)
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:3534 errors:0 dropped:0 overruns:0 frame:0
TX packets:3534 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:222967 (222.9 KB) TX bytes:222967 (222.9 KB)
wlan0 Link encap:Ethernet HWaddr 48:d2:24:a9:16:fd
inet addr:192.168.0.79 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::4ad2:24ff:fea9:16fd/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:18608 errors:0 dropped:0 overruns:0 frame:0
TX packets:11166 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:24376233 (24.3 MB) TX bytes:1306372 (1.3 MB)
Ну вот и все))) Кстати, имя драйвера, если кому пригодится: ath9k. Радуемся)))Автор: Роман Дмитриевич
Всем, кто увидел первую версию поста — цифры были кривые из-за turbo boost
Возьмем простой пример tcp клиента и сервера на python. Сервер создает пул из N потоков и ждет соединений с клиентом. Получив соединение передает его на обработку в пул. На каждом соединении сервер ждет от клиента строку данных и имитирует некую обработку. При получении 'byen' сервер завершает обработку клиента.
Клиент открывает N соединений с сервером и генерирует на них нагрузку. Общий объем нагрузки за один запуск клиента фиксирован.
data = ' ' * 100 + 'x0A'
def client(th_count):
sockets = []
for i in range(th_count):
sock = socket.socket()
for cnt in range(3):
try:
sock.connect(host_port)
break
except socket.error:
if cnt == 2:
raise
time.sleep(0.1)
sockets.append(sock)
for i in range(NUM_PACKETS):
sock = random.choice(sockets)
sock.send(data)
for sock in sockets:
sock.send('byex0A')
data = ' ' * 100 + 'x0A'
def client(th_count):
sockets = []
for i in range(th_count):
sock = socket.socket()
for cnt in range(3):
try:
sock.connect(host_port)
break
except socket.error:
if cnt == 2:
raise
time.sleep(0.1)
sockets.append(sock)
for i in range(NUM_PACKETS):
sock = random.choice(sockets)
sock.send(data)
for sock in sockets:
sock.send('byex0A')
def server(th_count):
def process_client(sock):
num = 0
while True:
msg = ""
while not msg.endswith('n'):
msg += sock.recv(1)
if msg == 'byen':
break
for i in range(serv_tout):
pass
num += 1
s = socket.socket()
s.bind(host_port)
s.listen(5)
with ThreadPoolExecutor(max_workers=th_count) as pool:
fts = []
for i in range(th_count):
sock,_ = s.accept()
fts.append(pool.submit(process_client, sock))
for ft in fts:
ft.result()
def server(th_count):
def process_client(sock):
num = 0
while True:
msg = ""
while not msg.endswith('n'):
msg += sock.recv(1)
if msg == 'byen':
break
for i in range(serv_tout):
pass
num += 1
s = socket.socket()
s.bind(host_port)
s.listen(5)
with ThreadPoolExecutor(max_workers=th_count) as pool:
fts = []
for i in range(th_count):
sock,_ = s.accept()
fts.append(pool.submit(process_client, sock))
for ft in fts:
ft.result()
Замеряем сколько времени нужно для одного прогона этой системы с N=4
$ python mt_test.py client 4 & time python mt_test.py server 4
real 0m8.342s
user 0m7.789s
sys 0m6.587s
$ python mt_test.py client 4 & time python mt_test.py server 4
real 0m8.342s
user 0m7.789s
sys 0m6.587s
А теперь почти то же самое, но разрешим операционной системе исполнять все потоки сервера только на одном ядре из 8ми доступных
$ python mt_test.py client 4 & time taskset 0x00000001 python mt_test.py server 4
real 0m4.663s
user 0m3.186s
sys 0m0.762s
$ python mt_test.py client 4 & time taskset 0x00000001 python mt_test.py server 4
real 0m4.663s
user 0m3.186s
sys 0m0.762s
Уличная магия в действии — многопоточная программа исполнилась в 2 раза быстрее, когда мы разрешили использовать только одно ядро процессора.
Почему такое получилось? Во-первых GIL — сколько бы потоков в питоне мы не создали, они всегда будут исполняться питоновский код только по очереди. Питон не позволяет двум потокам одного процесса одновременно исполнять свой байтокод.
Таким образом для этой программы(как и для 99% программ на питоне) никакого заметного ускорения от использования более одного ядра ожидать и не приходится. Все чисто питоновские программы конкурентны, но не параллельны. А конкурентной такой системе от изменения количества ядер в процессоре не холодно и не жарко (почти).
Почему же скорость исполнения падает, если использовать более одного ядра? Причин две:
Итак что происходит: пусть у нас есть два потока, один из которых(первый) сейчас обрабатывает принятые данные, а второй ожидает данных от сокета. Наконец второй поток получает данные и ОС готова продолжить его исполнение. Она смотрит на доступные ядра, видит что первое ядро занято первым потоком и запускает второй поток на исполнение на втором ядре. Второй поток запускается и первым делом пытается захватить GIL. Неудача — GIL захвачен первым потоком. Второй поток снова засыпает, ожидая освобождения GIL.
В итоге операционная система, которая понятия не имеет ни о каких GIL, сделала кучу лишней работы (переключение контекста достаточно дорогая операция). Правда заметная часть этой работы делалась вторым ядром, так что происходила параллельно и почти не мешала исполняться первому потоку. Почти — потому что второе ядро все равно занимало шину памяти. Ситуация становится хуже, если в системе есть HT — в этом случае второе ядро может делить с первым исполняемые блоки процессора и все эти лишние переключения будут серьезно замедлять исполнение первого потока.
Вторая проблема состоит в том, что второй поток переброшен на исполнение на второе ядро процессора. Когда первый поток освободит GIL
, то второй поток продолжит исполнение на втором ядре, потому что ОС знает, что кеши первого и второго уровня у каждого ядра свои и старается без причин не гонять потоки между ядрами. В итоге все имеющиеся потоки «размазываются» по доступным ядрам. Съедая в сумме 100% одного ядра, они превращают это в 12.5% на каждом из 8ми ядер. При этом в промежутках пока питоновские потоки ждут GIL на эти ядра вклиниваются другие потоки из системы, постоянно вытесняя наши данные из кеша.
В итоге питоновские потоки постоянно «бегают» по ядрам. Данные копируются в кеш и из кеша, а каждый кеш-промах стоит до тысяч тактов на обращение к RAM. Даже по меркам питона — серьезные нагрузки.
Выставив привязку к одному ядру мы убиваем сразу двух зайцев. Во-первых сокращаем количество переключений контекста, поскольку ОС будет заметно реже запустить на исполнение второй поток, если единственное доступное ему ядро сейчас занято. Во-вторых другие потоки не будут вклиниваться на это ядро, тем самым мы уменьшим интенсивность обмена данными между кешем и ОЗУ (питоновские потоки в одном процессе используют заметную часть данных совместно).
Итоги тестирования.
Все измерения сделаны на Core i7-2630QM@800MHz, python 2.7.5, x64, ubuntu 13.10 с усреднением по 7ми выборкам. Долгая война с turbo boost окончилась принудительным переводом процессора в режим минимальных частот.
-------------------------------------------------------------------------
| Потоки | SUM SUM_AF %DIFF | SYS SYS_AF %DIFF | USR USR_AF %DIFF |
-------------------------------------------------------------------------
| 1 | 3.35 3.55 -5 | 0.54 0.52 4 | 2.78 3.03 -8 |
| 2 | 7.26 4.63 36 | 4.91 0.67 86 | 5.10 2.95 42 |
-------------------------------------------------------------------------
| 4 | 8.28 4.90 41 | 6.58 0.76 88 | 7.37 3.14 57 |
| 8 | 7.96 5.00 37 | 6.49 0.84 87 | 7.32 3.15 57 |
-------------------------------------------------------------------------
| 16 | 9.77 5.88 40 | 6.53 0.73 89 | 7.01 3.15 55 |
| 32 | 9.73 6.84 30 | 6.54 0.81 88 | 7.06 3.04 57 |
-------------------------------------------------------------------------
Прогон теста по VTune показывает, что после выставления привязки количество кеш промахов уменьшается примерно в 5 раз, а количество переключений контекста — в 40. В ходе экспериментов обнаружилась еще одна интересная вещь — при выставлении привязки к одному ядру более эффективно используется turbo boost, что тоже ускорит вашу программу, если больше никто не грузит систему. Для этого теста turbo boost был заблокирован.
Будет ли что-то подобное в других случаях? Хотя данная программа и обрабатывает данные, приходящие из сокета, но данные приходят быстрее, чем она может их обработать. Таким образом она является CPU bounded. Если программа будет в основном занята ожиданием данных, то выставления привязки к ядру даст меньше ускорения — ОС будет меньше перебрасывать потоки между ядрами. Чем выше нагрузка на процессор, тем больше будет выигрыш.
Когда мы можем получить замедление:
P.S. В 3.3 поведение все то-же.
P.S.S. Нашел уже готовую статью про тоже самое.
Ссылки:
habrahabr.ru/post/84629
habrahabr.ru/post/141181
vimeo.com/49718712
stackoverflow.com/questions/868568/cpu-bound-and-i-o-bound
ru.wikipedia.org/wiki/Hyper-threading
Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.
Автор: konstantin danilov
# порт для HTTP (подписи, SOAP, передача мультимедиа) траффика
port=8200
# сетевые интерфейсы для работы сервера, разделяйте запятыми
network_interface=wlan0
# укажите директории для сканирования.
# * можете у