Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Что такое Boilerplate code?

Читая разные статьи иногда сталкиваюсь с таким выражением :
Boilerplate code
В моей команде внятно никто не смог объяснить это понятие. Что же это такое?


Ответ

В Википедии же всё написано
Понятие boilerplate code или boilerplate относится к секциям кода, которые должны быть написаны во многих местах с минимальными изменениями. Часто используется по отношению к языкам, в которых программист должен написать много кода, чтобы выполнить минимальную задачу.
Многие современные IDE автоматически генерируют boilerplate код. Также существует такая вещь, как скаффолд (scaffold), который позволяет генерировать boilerplate (например, в Ruby on Rails можно генерировать базовые CRUD методы в контроллерах).
Примеры:
html
Что такое Boilerplate code?
Boilerplate code, который нам необходимо повторять в каждом шаблоне. (Пруф)
JAVA
public class Pet { private String name; private Person owner;
public Pet(String name, Person owner) { this.name = name; this.owner = owner; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Person getOwner() { return owner; }
public void setOwner(Person owner) { this.owner = owner; } }
P.S.: это главная причина, по которой я в своё время отказался от программирования на Java.

Как использовать маски слоев (layermask) и для чего пишут 1 << layer?

Доброго времени суток!
Я разбираюсь в работе Unity, смотрю уроки, некоторые скрипты и иногда встречаю конструкцию вида 1 << layer, где layer является целым числом. Чаще это встречаю в методах, связанных с физикой, например Raycast
public int groundLayer = 5;
void SomeMethod() { // do smth... if (Physics.RaycastAll(transform.position, transform.forward, 100.0f, 1 << groundLayer).Length > 0) { ... } // do smth... }
И не только с физикой:
LayerMask Collisionmask; int layer = 5;
void SomeMethod() { // do smth... if ((Collisionmask.value & (1 << layer)) == 0) { ... } // do smth... }
Читал, что это какие-то маски. Они указывают на слои. Но мне не понятно, что это за маски, как их использовать, причем тут вообще слои? Что же такое 1 << layer, например?
Может кто-нибудь объяснить подробно? Спасибо.


Ответ

Запаситесь попкорном и колой (пирожками и чаем). Это будет долго, но познавательно. ))
Что это и к чему?
В Unity, как мы все (или не только лишь все) знаем, имеются слои (layers). Освежить память о том, что это (а если не знаете, то прочитать и ознакомиться) можно в документации. Используются они по-разному. К примеру в 2D с их помощью можно обозначить и отделить задний/средний/передний план. Что-то будет сзади, что-то спереди. В более сложных играх слоёв может быть больше и объекты можно группировать по этим слоям. Например, можно сделать слой для NPC и выделить из него слой для враждебных NPC - Enemies и прочие.
В общем случае манипуляции с layerMask (маской) позволяют выяснить, находится ли проверяемый объект (GameObject) в указанном слое или нет (неважно, что это за объект и каким образом он проверяется). В зависимости от результата проверки производить задуманные действия.
Рассмотрим, к примеру, Raycast. Если обратимся к документации, то можно увидеть, что метод выглядит так:
public static RaycastHit2D Raycast(Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
Один из параметров: int layerMask = DefaultRaycastLayers. Работает он так: из некой точки в каком-то направлении выпускается луч (допустим это такая магия на расстоянии), этот луч падает на какую-то преграду (объект) и, например, если этот объект находится в слое Enemies - ушмякать его. О примере (Collisionmask.value & (1 << layer)) == 0 поговорим ниже. Но суть у него аналогичная: определить принадлежность за счет маски > сделать что-то в зависимости от...
Надеюсь с сутью понятно, пойдем далее.
Как это работает.
Количество слоев в Unity: 32. Нумеруются они с 0 до 31.

LayerMask (маска) - представляется в виде 32-битного целого числа:
0000 0000 0000 0000 0000 0000 0000 0000
Я отделил каждые 4 бита для лучшей читабельности.
Маска использует свои 32 бита для представления различных флагов (состояние объекта, например: 1 - включен, 0 - выключен). В данном конкретном случае каждый флаг (бит) представляет собой конкретный слой. Возьмем для разбора первые 8 слоев и запишем двоичное представление каждого слоя (Первый бит (младший) находится с правой стороны).
слой № Двоичное Десятичное ---------------------------------- Layer 0 = 0000 0001 = 1 Layer 1 = 0000 0010 = 2 Layer 2 = 0000 0100 = 4 Layer 3 = 0000 1000 = 8 Layer 4 = 0001 0000 = 16 Layer 5 = 0010 0000 = 32 Layer 6 = 0100 0000 = 64 Layer 7 = 1000 0000 = 128
Вот что представляют из себя слои в двоичном представлении. Как видим, конкретный слой определяется одним конкретным битом.
Теперь пробуем разбираться на примере if ((Collisionmask.value & (1 << layer)) == 0)
Мы видим переменную layer, которая является целым числом. Эта переменная — номер слоя (0, 1, 2, 3, 4 ...), НЕ десятичное и НЕ двоичное представление. Collisionmask в инспекторе представляет собой выпадающий список со слоями:

А Collisionmask.value в свою очередь является двоичным представлением комбинации выбранных слоёв (так как выбрать можем не один а несколько слоев). Для понимания: например, если мы хотим создать маску для слоя #2 и #5, то двоичное представление будет такое:
0010 0100 // #2,#5
или например слои #1, #2, #5, #6 будут представлять из себя:
0110 0110 // #1,#2,#5,#6
Что мы хотим? Мы хотим узнать, входит ли в эту маску проверяемый нами слой layer? И в зависимости от этого что-то сделать. В примере выше мы хотим зайти в условие и что-то сделать, если наш объект, находящийся в слое 5, не входит в указанную маску.
Допустим LayerMask = 0110 0110; // #1,#2,#5,#6, а номер нашего слоя int layer = 5;
Нужно слой 5 привести к его двоичному представлению, соответствующее в слоях. Посмотрим в табличку вверху. Слой 5 имеет десятичное представление равное 32. Оно, в свою очередь, в двоичном представлением является 0010 0000. Оно получается за счет применения побитого оператора сдвига влево << относительно самого младшего бита (0000 0001) именно на 5 значений (фантастика!). Сдвигаем единичку 5 раз влево:

Как видим получилось число 0010 0000 и оно действительно входит в нашу маску 0110 0110, потому что маска содержит единичку в позиции номер 6 справа, и слой 5 имеет единичку на такой же позиции. Если не верите на слово, то проверить легко: надо произвести побитовое умножение (побитовое И "&") между маской и проверяемым значением. Это, кстати, и делается в строке Collisionmask.value & (1 << layer)
0110 0110 // #1,#2,#5,#6 & (and) 0010 0000 // #5 --------- 0010 0000
Видим, что входит в маску. Если бы не входил, то побитовое умножение дало бы результат 0. Как пример доказательства, можем проверить входит ли слой 7 в данную маску или нет. Сдвигаем младший бит на 7 позиций влево:

Получаем 1000 0000. Проверяем
0110 0110 // #1,#2,#5,#6 & (and) 1000 0000 // #7 --------- 0000 0000
Видим 0. Не входит. И раз оно не входит, значит срабатывает наше условие сверху:
if ((Collisionmask.value & (1 << layer)) == 0) => if (0 == 0) => true
значит мы заходим в условие и делаем что нам нужно. Это то, что мы и искали.

Дополнение 1
Помимо проверки конкретного слоя на вхождение в маску, можно проверять, наоборот, все слои, кроме указанного. Нужно только использовать, так скажем, оператор инверсии (~). Он меняет в битовом представлении операнда 0 на 1, а 1 - на 0. Таким образом ~00000010 будет равен 11111101, что будет тестировать каждый слой, кроме #2. Если хотите исключить несколько слоев, то нужно будет скомбинировать маски через оператор "ИЛИ":
int layer1 = 3; int layer2 = 5; int layermask = ~((1 << layer1) | (1 << layer2)) // НЕПРАВИЛЬНО: ~(1 << layer1) | ~(1 << layer2)

Дополнение 2
На самом деле не всегда нужно писать 1 << что-то. Например в Raycast можно в параметр передать Collisionmask.value, где Collisionmask имеет тип LayerMask, а он, в свою очередь, в инспекторе выглядит как выпадающий список (см. изображение №2). Очень удобно.
Также не стоит хардкодить числами, т.е. не писать 1 << 7 или 1 << 5, т.к. можно забыть, что это за 5 и 7. Для того, чтоб узнать нужное число слоя, можно пользоваться LayerMask.NameToLayer, пример:
int npcLayer = LayerMask.NameToLayer("NPCLayer"); int npcMask = 1 << npcLayer;
и тоже самое при "групповых" масках:
int npcGoodLayer = LayerMask.NameToLayer("NPCGood"); int friendsLayer = LayerMask.NameToLayer("Friends"); int npcGoodMask = 1 << npcGoodLayer; int friendsMask = 1 << friendsLayer; int finalmask = npcGoodMask | friendsMask; // Or, (1 << npcGoodLayer) | (1 << friendsLayer)
Дополнение 3
На самом деле такая конструкция 1 << ... встречается не только в Юнити. Это известная практика вообще в программировании. К примеру есть у какого-либо производителя продукт, приложение и он предоставляет API для других программистов.
API - набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) или операционной системой для использования во внешних программных продуктах. Используется программистами при написании всевозможных приложений. (c) Википедия
У API могут быть ограничения на доступ различного рода. Программист указывает маску, к чему он хочет получить доступ, а на сервере приложения проверяется это и в случае несовпадения по маске - выдает предупреждение или еще что-либо. Самый простой пример Вконтакте и его права доступа для API

Там указано десятичное представление, но сути не меняет.

Volatile в многопоточной программе

Всем доброго дня! Хотелось бы узнать мнения, стоит ли использовать volatile в многопоточных программах на C/C++? Бывают ли на практике ситуации, когда его использование может быть более предпочтительным использованию мьютексов? Если у кого-то есть подобный положительный опыт его использования, поделитесь примерами, пожалуйста.


Ответ

Volatile и мьютекс не имеют ничего общего. Volatile лишь означает, что доступ к переменной не будет оптимизироваться компилятором. В мультиядерной (обращаю внимание, не просто мультипоточной!) системе это имеет значение: демонстрация сбоев программы при отсутствии барьеров памяти
Volatile в этом примере могла бы обеспечить правильный порядок инструкций чтения/записи, так что ответ скорее - положительный, т.е. да, на многоядерных системах, когда доступ к переменной может быть неожиданным из другого потока, ее следует помечать volatile, это избавит Вас от множества проблем.
Только надо не забывать, что существует 2 типа барьеров памяти: барьеры процессора и барьеры компиляции. Volatile позволяет избегать только барьеров второго типа, поэтому может более наглядным примером будет Memory ordering, barriers, acquire and release semantics (Compiler re-ordering)
Комментарии:
однако однозначного мнения о его использовании в многопоточке не нахожу
Ну, скажем так: если Вы точно не можете сказать, какой порядок отношений будет между потоками касательно определенной переменной, то ее следует пометить volatile, чтобы не заморачиваться с барьерами компиляции _ReadBarrier/_WriteBarrier/_ReadWriteBarrier. Но если Вы уже определились с этим порядком, то правильно-расставленные барьеры будут гораздо эффективнее volatile, т.к. позволят все же оптимизировать доступ к этой переменной.
Я лично понимаю это так: если программа готова, то можно заняться заменой volatile на барьеры, это может добавить скорости ее работы. Нет - не стоит заморачиваться, используйте volatile, чтобы программа просто работала правильно.
я так помнимаю, volatile в первом примере имеет смысл именно в "самодельном мьютексе"?
В первом примере, volatile касается sharedValue, модификация которой может "выпрыгнуть" из критического участка кода из-за переупорядочивания к ней доступа каким-нибудь компилятором. Но не используется в примере потому, что acquire/release семантика в новом стандарте уже учитывает барьерную синхронизацию на уровне компилятора (см. реализацию atomic с используемой в ней _ReadWriteBarrier).
Т.е., если бы вместо flag здесь стоял какой-нибудь самодельный синхронизатор, пусть даже реализованный на том же на CAS'е, но не использующий acquire/release, проблемы были бы сразу на 2-х фронтах.
А вот вместо мьютекса (или семафора) для синхронизации на практике использовать переменные не стоит (как бы создатели компилятора их не называли). И дело тут в логической сложности подобных "алгоритмов", а не в свойствах переменных, переупорядочении команд, кэш-когерентности и т.п.
@avp, конечно, логика тут не на последнем месте. Но на практике, Вы даже такую "самодельную синхронизацию" завернете в функцию, очевидно, ради элементарного удобства. Логически для самого программиста этим все и решится.
Проблема "самопальной" синхронизации шире: чаще всего она инлайнится. Всем понятен смысл? А надежность API для синхронизации доступа к переменным обуславливается в основном еще и тем, что большинство (если не всех) компиляторов строят свои гипотезы по оптимизации хранения значений между вызовами функций, т.е. считается, что если между операторами доступа к переменной стоит вызов функции, то через этот вызов значение переменных не будет переноситься в регистрах. Это свойство автоматического барьера памяти само по себе может решать проблемы доступа. А inline все это дело приводит в неопределенность. Поэтому "самопальную" синхронизацию надо на 100 раз перепроверять, и лучше - не вручную, а какими-то инструментами, типа Relacy Race Detector, Viva и т.п.
На мой взгляд стоит пользоваться критическими секциями. Они шустры, однозначны и понятны.
@mikelsv, критические секции, как бы ни были понятны, шустры только за счет spinlock'ов, которыми пользуется процентов 10-20 из всех, кто что-то знает о критических секциях (быть может, моя оценка оптимистична). Такие блокировки уже сегодня далеко не шустры. А у Intel'а в планах еще Haswell и Skylake. По Haswell'у известно, что она будет расширена TSX, которая предусматривает неблокирующую работу потоков в т.н. "регионах транзакции", в которых потоки хардварно взаимоконтролируют друг друга на предмет одновременного доступа к ресурсу "на ходу", т.е. без простоя на блокировках.
Для примера такой конкуренции, можете посмотреть на Хэшкоде мой вариант lock-free-стека. Здесь есть 2 транзакции, перед CAS'ами, т.е. в случае неуспешного CAS'а, каждая из транзакций откатывается в начало тела цикла и повторяется до успешного исхода.
Restricted Transactional Memory (RTM) is a new instruction set interface that provides greater flexibility for programmers. TSX enables optimistic execution of transactional code regions. The hardware monitors multiple threads for conflicting memory accesses and aborts and rolls back transactions that cannot be successfully completed. Mechanisms are provided for software to detect and handle failed transactions.
Такая техническая возможность процессоров скорее всего потребует смены парадигмы синхронизации потоков, в которой банальная блокировка ресурса просто не выживет, т.к. будет бить по производительности параллельных систем, к которым мы постепенно, но очень уверенно движемся.
а в ситуации, когда значение переменной было установлено до расщепления на потоки, а затем только считывается (тоесть далее в течение всего времени работы программы эта переменная значение не меняет), одного volatile достаточно ведь?
@margosh, по большому счету, volatile в этом случае не требуется. Есть небольшая вероятность, что значение может быть записано после запуска потоков. Ее можно ликвидировать, если после записи в эту переменную поставить барьер _WriteBarrier. Или _ReadWriteBarrier для перестраховки, перед самим запуском потоков, особенно, если с ней что-то делается в основном потоке. Так у компилятора будет больше возможностей к оптимизации.
Если перекладывать барьеры на семантику acquire/release, то барьер можно заменить установкой значения Вашей переменной с помощью _InterlockedExchange_rel, а в потоках читать ее через _InterlockedCompareExchange_acq( &value, x, x ), где value - это непосредственно переменная, которая не меняется в потоках, а x - самая маловероятная величина, которой может быть равна value, например, для неотрицательных value выбирают x = -1, чтобы не портить кэши. Не особо удобно, да? :) Тем не менее, считается, что это будет эффективнее volatile и барьера, т.к. мы явно сказали компилятору, где будет барьерная запись и чтение переменной
И как быть с чем-то вроде pipe, которые из разных потоков пишутся? Доступ к ним синхронизировать не нужно, но нужно ли их делать volatile?
А можно поконкретнее, что из себя представляют Ваши "pipe"? В WinAPI это обычный файловый дескриптор, его значение не меняется при записи/чтении. Соответственно, volatile ему не нужен.
@mega, учтите в своих ответах, что у @margosh программа в FreeBSD.
Да, @avp, я тоже заметил комментарий. Сам я программирую для Windows, но сейчас речь идет в основном о компиляции, и о том, какими средствами ее контролировать. Поэтому не вижу проблем, даже когда опираюсь на виндовые интринсики _Interlockedxxx, т.к. их аналоги есть везде. Ну а их версии с acq/rel - судите сами, если уж они даже в стандарте прописались :)
Второй не может начать читать, пока ему не прийдет информация из pipe, но если она прийдет раньше, чем был записан флаг (изменение порядка операций в одном потоке) - алгоритм работы нарушится.
@margosh, если запись в пайп будет производиться после установки признака, то обязательно будет такая ситуация, что второй в это время будет подчищать мусор и как раз наткнется на этот самый признак, но до того, как первый пошлет что-либо в пайп. Если для Вас это штатная ситуация, то все хорошо, но если первый в это время будет пользоваться чем-то, что подчищает второй?!?! Думаю, ничего из этого хорошего не выйдет, тут нужна блокировка!
free-поток удаляет из списка только одну структуру, и ждет что-то от pipe снова, тоесть от 2 потоков прийдет 2 признака и удалится поочередно 2 структуры. Порядок удаления значения не имеет, так как это последние действия при завершении потоков : установить флаг, бросить признак в pipe.
Если у Вас что-то вроде такого:
pThreadStruct->CanFree = 1; SendToPipe( 1 );
то порядок действительно не имеет значения, но если вот такое:
pThreadStruct->CanFree = 1; SendToPipe( pThreadStruct->... );
то здесь обязательно будет ошибка, т.к. CanFree = 1 можете понимать как delete pThreadStruct, а удаление только одной структуры в ответ на сигнал из пайпа совсем не означает, что удалена будет именно та структура, чей поток послал в пайп признак.
По поводу оптимизации и перестановок в коде : тоесть, присвоение может поменяться местами с захватом блокировки, так как функция блокировки с точки зрения компилятора с защищаемой переменной никак не свзяна?
Да, совершенно верно!
Хорошо, но как на это повлияет использование этой переменной с volatile?
volatile "скажет" компилятору, что к этой переменной нельзя применять "перестановки", т.е., если вы поместили запись в эту переменную в пределах критического участка, значит она из него и "не выпрыгнет" ни при каких условиях.
Другое дело, что она и не в критическом участке никуда прыгнуть не сможет, а учитывая специфику процессоров, конвейеры которых обычно оптимизированы на параллельную обработку сразу нескольких команд но в определенном порядке, то компилятор не сможет такую запись или чтение volatile оптимизировать для таких процессоров (ради этого и производятся перестановки, чтобы уменьшить совокупное число тактов на работу алгоритма, в этом и заключается оптимизация по скорости).
@margosh: мне кажется, в этом обсуждении ошибка. В компилятор обязана быть встроена магия (то есть конечно подходящая #pragma), которая запрещает перенос операций за границы mutex_lock/unlock, иначе эти конструкции просто не смогут правильно работать.
Про автоматические барьеры между вызовами функций я уже писал. Это и есть Ваша "магия". И естественно, без лишних напоминаний об этом препроцессору, через #pragma. :) Про какое обсуждение @VladD, Вы сейчас говорите, не понял?
@mega: про обсуждение выше в комментариях: о том, что присвоение переменной может при оптимизации вылететь за пределы области защищённой mutex'ом
Если защита в инлайне и без погружения в ядро - очень легко может вылететь. Я просто не знаю, на чем основаны мьютексы у @margosh и может ли их разворачивать компилятор. Если может, то вполне логичные результаты:
при безуспешных попытках отладки программы под пока недопиленным FreeBSD-Valgrind, очень удивилась, когда увидела среди кучи сообщений о блокировках с развернутым стеком вызовов, захват блокировки уже после присвоения

@mega: тут есть две вещи. Во-первых, #pragma должна запретить кодогенератору перемещать присваивание.
Я знаю только об одной прагме, которая потенциально могла бы такое делать: #pragma optimize, но по ней нет данных о том, что она будет именно блокировать перемещения. Возможно, конечно, но скорее всего это будет вкупе с отключением общей оптимизации, что еще хуже, чем volatile
Если Вы о такой прагме, то это очень странный совет, особенно на фоне того, что уже было сказано о volatile
Во-вторых, сам mutex генерирует полный memory barrier
Вот мне почему-то кажется, что внутренняя реализация подобного рода объектов дело исключительно платформозависимое. И не будет в большинстве случаев так категорична. В POSIX ведь не даром их реализация не закреплена. Поэтому Ваши доводы по поводу наличия каких-либо барьеров в недрах "абстрактного" мьютекса скорее всего неверны
p.s.: у меня уже давно закончились комментарии, а новый ответ я начать не могу, так что, пожалуйста, обращайте внимание на мои комментарии, которые я оставляю при редактировании ответа.

Интересные ресурсы для программиста [закрыт]

Привет всем. Я тут задался мыслью, что каждый день я читаю хабр, и по большому счету больше ничего. В основном статьи связанные с программированием. Пытался искать что-нибудь подобное (интересное и актуальное), но поиск не дал должных результатов. Есть еще несколько ресурса довольно не плохих: http://www.smashingmagazine.com/ - но из программирования там только js, http://net.tutsplus.com/ - здесь более разнообразно. Ну и http://www.html5rocks.com например, но и он тоже в основном js. Так что же я всё таки хочу спросить у вас: быть может вы раскроете мне глаза - от куда черпать интересную информацию, а было бы прекрасно если бы вы поделились rss лентами в вашем ридере. Думаю не мне одному будет интересно узнать о полезных ресурсах. Заранее спасибо :)


Ответ

Скорее не для общения, а для самообразования могут пригодиться. Институт инженеров электротехники и электроники (IEEE) Coursera Интуит

Чем отличаются ссылки от указателей в С++

В чем принципиальное отличие ссылки от указателя в С++? Когда лучше использовать ссылку, а когда указатель? Какие ограничения есть у первых, а какие у вторых?


Ответ

Еще отличия
Нельзя объявить массив ссылок. У ссылки нет адреса. Существует арифметика указателей, но нет арифметики ссылок. Указатель может иметь «невалидное» значение с которым его можно сравнить перед использованием.
Если вызывающая сторона не может не передать ссылку, то указатель может иметь специальное значение nullptr
void f(int* num, int& num2) { if(num != nullptr) // if nullptr ignored algorithm { } // can't check num2 on need to use or not }
http://rextester.com/EQMC52074
(Standart) A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Ссылка не обладает квалификатором const
#include int main() { std::cout << "Hello, world!
";
const int v = 10; //int& const r = v; // Ошибка const int& r = v;
enum { is_const = std::is_const::value };
if(!is_const) std::cout << "const int& r is not const
"; else std::cout << "const int& r is const
"; }
О весёлом
Некоторые ссылаются на отрывок с интервью с Страуструпом:
Очевидной реализацией ссылки является (константный) указатель, при каждом использовании которого происходит разыменование. В некоторых случаях компилятор может оптимизировать ссылку таким образом, что во время исполнения вообще не будет существовать объекта, представляющего ссылку.
Другие задают в ответ лишь в один вопрос:
Чем является реультат разыменовывания указателя?
На тему, нужно ли знать отличия указателя от ссылки, писал Джоэл Спольски в своей статье «Закон Дырявых Абстракций».

Какова структура файла байткода CPython?

Пытаюсь разобраться в структуре скомпилированного байт-кода CPython. Допустим, у меня есть файл foo.py следующего содержания:
def hello(name): print("Hello, %s" % name)
Скомпилированный __pycache__\foo.cpython-35.pyc выглядит так:

Дальше что я понял:
16 0d 0d 0a - это магическое число 06 e2 7f 57 - дата последнего изменения 31 00 00 00 - размер файла (должен быть - хотя в файле 216 байт, так что я не знаю, что это за размер на самом деле) следующие 22 байта (e3 00 00 00 ... 00 00 00 73) - не знаю для чего 10 00 00 00 - видимо размер кода модуля (но тогда получается что предыдущий символ должен быть типом, а это 73 - код строкового типа) 64 00 00, 64 01 00 - LOAD_CONST(0); LOAD_CONST(1), где константа 0 - code object функции, константа 1 - её имя ("hello") 84 00 00 - MAKE_FUNCTION(0), только я так и не понял, зачем нужен аргумент этого опкода 5a 00 00 - STORE_NAME(0), где имя 0 - имя функции ("hello") 64 02 00 53 - LOAD_CONST(2); RETURN_VALUE, где константа 2 - None (правда не совсем понятно зачем модулю возвращать что-то) 29 03 - кортеж констант модуля: (, "hello", None) следующие 18 байт (74 00 00 64 ... 64 00 00 53) - код функции hello (тот который hello.__code__.co_code) 29 02 - кортеж констант функции: (None, "Hello, %s") 4e - тип константы NONE 7a 09 - тип строковой константы (SHORT_ASCII) и длина строки "Hello, %s" (9 символов) 48 65 6c 6c 6f 2c 20 25 73 - строка "Hello, %s" 29 01 - кортеж имён функции: ("print",) da 05 - здесь вроде должен быть один из строковых типов, но вместо него несуществующий тип da; 05 - длина строка 70 72 69 6e 74 - имя функции print 29 01 - кортеж имён локальных переменных: ("name",) da 04 - опять неизвестный тип da и длина строки 04 6e 61 6d 65 - имя переменной name a9 00 72 03 00 00 00 - не понял fa 06 - ещё один непонятный тип fa и длина строки 06 66 6f 6f 2e 70 79 - имя файла модуля (foo.py) da 05 - непонятный тип da и длина строки 05 68 65 6c 6c 6f - имя функции hello (правда нет объявления кортежа имён модуля или вроде того) следующие 17 байт (01 00 00 00 ... 00 00 00 4e) - не понял 29 01 - какой-то кортеж длиной в один элемент 72 ... - константа типа ref (что это за тип?) da 08 - снова загадочный типа da и длина строки 08 3c 6d 6f 64 75 6c 65 3e - строка "" (зачем?) 01 ... - дальше не знаю что
Помогите восполнить пробелы. Если бы мне надо было просто узнать значение какой-нибудь константы, то мне бы имеющихся знаний хватило. Но я пытаюсь написать интерпретатор байт-кода Python, так что мне надо полностью разобраться со структурой .pyc-файлов.


Ответ

История длинная - можно поставить чайку. Также у меня нет возможности копировать все из документации в ответ, так что документацию придется читать самому. Также не будет экспериментов с dis - предполагается, что автор вопроса уже наигрался с ним и решил копнуть глубже.
Вопрос объемный, но так как он пользуется популярностью, я немного коснусь этой темы для одной выбранной реализации и одной выбранной версии - CPython 3.6.1 - эта версия, потому что она последняя и ответ устареет позднее, и потому что в вопросе явно указана версия 3.5.
В общем-то в последних версиях довольно легко разобраться, что к чему - нет никаких сложностей, которые могут сорвать вам колпак. Исходной точкой будет модуль py_compile. Почему с нее, а не с C исходников? - потому что вот что говорит документация о нем: Этот модуль предоставляет функцию для генерации байт-кода из исходников. То, что доктор прописал.
Также заранее напишем тестовый пример, который и будет подвергнут разбору и определим его в отдельный файл test.py
import time
def my_gen(): for i in range(10): time.sleep(1) yield i**2
if __name__ == "__main__": for elem in my_gen(): print(elem)
Теперь в основном файле мы можем сгенерировать байткод вот так:
import py_compile
py_compile.compile("test.py", "test.pyc")
Расширение *.pyc - всего лишь расширение, соглашение о наименовании и может быть каким угодно, главное - это содержимое.
Запустить можно как обычно - python test.pyc
Теперь самое время обратить внимание на функцию, которая и сгенерировала байткод. Исходники вы можете посмотреть самостоятельно, я приведу краткую выдержку:
Первое, что там происходит - это определение пути и имени для выходного байткода. Мы указали это явно. Если нет, то путь определяется согласно PEP 3147/PEP 488 и еще и от версии зависит. Исходник в виде текста преобразуется в байты. За это отвечает еще модуль - importlib, а точнее класс importlib.machinery.SourceFileLoader. Внезапно, весь код, который там выполняется - это
with _io.FileIO(path, 'r') as file: return file.read()
то есть исходник просто читается. В нашем коде это выглядит как:
loader = importlib.machinery.SourceFileLoader('', file) source_bytes = loader.get_data(file) Исходники преобразовываются в AST - Абстрактное Синтаксическое Дерево. Невозможно тут написать, какой путь проходит исходник через череду всяких функций и проверок, но вкратце: вызывается функция compile
code1 = compile(source_bytes, file, mode='exec', dont_inherit=True) code = loader.source_to_code(source_bytes, file)
print(code, type(code))
print(code == code1)
Выше в коде я копировал их исходников py_compile (loader.source_to_code) и написал сам с использованием функции compile - результат в простом случае одинаков. Результатом явился экземпляр класса из модуля code Наконец, самое главное - преобразование AST в байткод. В py_compile это делается так:
source_stats = loader.path_stats(file) bytecode = importlib._bootstrap_external._code_to_bytecode( code, source_stats['mtime'], source_stats['size'])
sourcestats выглядит как-то так: {'mtime': 1493229661.7715623, 'size': 180}. mtime - это время изменения исходника, выраженное в каком-то особоточном timestamp'е. Пригодится еще далее.
Содержание code_to_bytecode
data = bytearray(MAGIC_NUMBER) data.extend(_w_long(mtime)) data.extend(_w_long(source_size)) data.extend(marshal.dumps(code))
Я был удивлен, но само преобразование в байткод занимает в общем-то 4 строчки. MAGIC_NUMBER - волшебное словцо, новое для каждой новой версии. Для 3.6.1 (Python 3.6rc1) - это (3379).to_bytes(2, 'little') + b'
'. Список всех волшебных словец можно найти в файле importlib/_bootstrap_external.py. Обратите внимание, что помимо числа туда еще засовывается перевод каретки. Функция _w_long - это (int(x) & 0xFFFFFFFF).to_bytes(4, 'little'). - можно проверить HEX редактором фактическое содержание .pyc файла - все совпадает. То есть сам код начинается после 2 + 2 + 4 + 4 байтов. Это все мелочи, настоящая цель - модель marshal и функция dumps. marshal - это модуль, содержащий функции, которые могут читать и писать значение переменных в бинарном формате. Формате специфичен для Python, но независим от архитектуры конечной машины Дальше, к сожалению, на питоне ехать не получиться, придется идти в исходники исходников: можно проследить за судьбой объекта в marshal.dumps, но в конечном итоге объект бьется на байты вот этой функцией. Она длинная, но относительно простая. Маршал может много всяких объектов засунуть в файл, но нас интересует объект типа code (его ему передали) - для этого есть специальная проверка на то, не является ли переданный объект кодом. Как видно из этой функции, безусловно пишется много какой информации - тип операции (op_code), аргументы, значения аргументов самые простые из этого списка. Что конкретно означают эти переменные, можно посмотреть (там комментов навалили сполна) в определении структуры PyCodeObject в файле code.h Еще, в общем-то, не все. Все только начинается. Мы можем посмотреть, ЧТО КОНКРЕТНО, по байтам, пишется в pyc файл и не придется лазить в C. Дело в том, что все нужные аттрибуты есть у питоновского объекта code. Я расположил аргументы в том порядке, в каком они пишутся в pyc:
args = ['co_argcount', 'co_kwonlyargcount', 'co_nlocals', 'co_stacksize', 'co_flags', 'co_code', 'co_consts', 'co_names', 'co_varnames', 'co_freevars', 'co_cellvars', 'co_filename', 'co_name', 'co_firstlineno', 'co_lnotab']
for arg in args: print(arg, getattr(code, arg, None))
Распечатав на экране значение аргументов, а также посчитав смещения (1 вызов w_long пишет 4 байта, W_TYPE пишет 1 байт) вы можете с легкостью, с помощью HEX редактора посмотреть что и куда пишется. Проанализировать co_code вы уже можете с помощью dis - в новых версиях у него появился козырный метод get_instructions
for instruction in dis.get_instructions(code): print(instruction)
В нем также есть и байтовое смещение, и код операнда и все, что может понадобиться. Для закрепления прочитанного можно своими руками полностью раскукожить pyc файл. Не буду писать весь парсер - дюже затратно, но из того, что написано уже можно написать парсер полностью:
from io import BytesIO import struct import datetime
byte1_little = b"byte1_big = b">c" byte2_big = b">h" byte4_big = b">I"
INTEGER_TYPE = int.from_bytes(b'i', 'little') CODE_TYPE = int.from_bytes(b'c', 'little') SHORT_TUPLE_TYPE = int.from_bytes(b')', 'little') NONE_TYPE = int.from_bytes(b'N', 'little')
def parse_string(raw_bytes): # Наш рулевой тут - https://github.com/python/cpython/blob/3.6/Python/marshal.c#L427 # Байтик на тип, четверочку на размер SIZE = int.from_bytes(raw_bytes.read(4), 'little') print("BYTES TO READ: ", SIZE) s = raw_bytes.read(SIZE) return s
def parse_code(raw_bytes): CO_ARGCOUNT = struct.unpack(byte4_little, raw_bytes.read(4))[0] co_kwonlyargcount = struct.unpack(byte4_little, raw_bytes.read(4))[0] CO_NLOCALS = struct.unpack(byte4_little, raw_bytes.read(4))[0] CO_STACKSIZE = struct.unpack(byte4_little, raw_bytes.read(4))[0] CO_FLAGS = struct.unpack(byte4_little, raw_bytes.read(4))[0] print("CODE STATS:", CO_ARGCOUNT, co_kwonlyargcount, CO_NLOCALS, CO_STACKSIZE, CO_FLAGS) CODE_TYPE = raw_bytes.read(1) # Тип строка assert CODE_TYPE == b's' CODE_ITSELF = parse_string(raw_bytes) return CODE_ITSELF
def parse_long(raw_bytes): VALUE = int.from_bytes(raw_bytes.read(4), 'little') print("LONG VALUE:", VALUE) return VALUE
def parse_none(raw_bytes): # Уже отпрасили return None
def parse_tuple(raw_bytes): # Размер у маленького - 1 байт, а не 4 # https://github.com/python/cpython/blob/3.6/Python/marshal.c#L471 SIZE = int.from_bytes(raw_bytes.read(1), 'little') print("TUPLE LEN:", SIZE) # Следующий тип - SHORT_TUPLE. Также по аналогии for index in range(SIZE): NEXT_TYPE = int.from_bytes(raw_bytes.read(1), 'little') if NEXT_TYPE == INTEGER_TYPE or NEXT_TYPE == INTEGER_TYPE | 128: print("LONG TYPE") parse_long(raw_bytes) # None 1 байт elif NEXT_TYPE == NONE_TYPE: print("NONE TYPE") parse_none(raw_bytes) # code elif NEXT_TYPE == CODE_TYPE or NEXT_TYPE == CODE_TYPE | 128: print("CODE TYPE") parse_code(raw_bytes) elif NEXT_TYPE == SHORT_TUPLE_TYPE: parse_tuple(raw_bytes)
with open("test.pyc", "rb") as f: raw_bytes = BytesIO(f.read())
MAGIC_NUMBER = struct.unpack(byte2_little, raw_bytes.read(2))[0] assert MAGIC_NUMBER == 3379 # пропускаем
raw_bytes.read(2) TIMESTAMP = struct.unpack(byte4_little, raw_bytes.read(4))[0] print(datetime.datetime.fromtimestamp(TIMESTAMP)) SIZE = struct.unpack(byte4_little, raw_bytes.read(4))[0] print(SIZE) OBJ_TYPE = int.from_bytes(raw_bytes.read(1), 'little') # Объект типа TYPE_CODE - см. https://github.com/python/cpython/blob/3.6/Python/marshal.c#L46 # Флаг 128 выставляется в функции https://github.com/python/cpython/blob/3.6/Python/marshal.c#L288 assert OBJ_TYPE == CODE_TYPE | 128 parse_code(raw_bytes) NEXT_TYPE = int.from_bytes(raw_bytes.read(1), 'little') # Small tuple if NEXT_TYPE == SHORT_TUPLE_TYPE: parse_tuple(raw_bytes)
Код несколько неполный, обрабатывается только несколько типов, но, надеюсь, сама идея понятна - этот код - зеркальное отражение функции dumps - только там использовался w_object (write object) и свои записывалки для простых типов, вам же нужно r_object (read object) и читалки для простых. r_object уже написан, остается только его внимательно прочитать.

String & StringBuilder

В таких языках как Java и C# для конкатенации большого числа строк принято использовать StringBuilder, чтобы получить линейную асимптотику вместо квадратичной.
Однако, JavaScript каким-то образом справляется имея лишь один тип String. Асимптотика конкатенации там линейная, по крайней мере при циклах до 131072 итераций. Но, что странно, время не зависит от длины складываемых строк. По крайней мере, так происходит в Хроме.
Как так вышло?
И бонусный вопрос знатокам JS: а что собственно случилось при 262144?
http://ideone.com/gtm5iy
using System; using System.Collections.Generic; using System.Diagnostics;
public class Program { private static string Test(int n, string s) { var res = "";
for (var q=0; q return res; }
public static void Main() { var res = new Dictionary[10]; const int N = 1024; var sw = new Stopwatch();
for (var n=0; n();
foreach (var s in new string[] {"!", "!2", "!234", "!2345678"}) { res[n][s] = 0;
for (var q=0; q res[n][s] /= N; } }
for (var n=0; n foreach (var kvp in res[n]) Console.Write("{0,10:0.000} ", kvp.Value / 1000);
Console.WriteLine(); } } }

0 1 0.001 0.000 0.000 0.000 1 2 0.002 0.001 0.001 0.001 2 4 0.003 0.003 0.005 0.003 3 8 0.006 0.006 0.007 0.007 4 16 0.013 0.015 0.013 0.014 5 32 0.025 0.025 0.032 0.034 6 64 0.050 0.059 0.070 0.097 7 128 0.120 0.142 0.220 0.303 8 256 0.337 0.417 0.630 0.972 9 512 0.897 1.256 1.964 4.087
К сожалению, при увеличении числа итераций программа не укладывается в 5 секунд, отведённые на выполнение на ideone. Вот результаты с домашнего компа:
D:\Temp\Supertemp>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe StringConcat.cs && StringConcat.exe Microsoft (R) Visual C# Compiler version 4.7.2046.0 for C# 5 Copyright (C) Microsoft Corporation. All rights reserved.
This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240
0 1 0.000 0.000 0.000 0.000 1 2 0.000 0.000 0.000 0.000 2 4 0.000 0.000 0.000 0.000 3 8 0.001 0.001 0.001 0.001 4 16 0.002 0.001 0.001 0.002 5 32 0.002 0.004 0.004 0.004 6 64 0.005 0.006 0.009 0.014 7 128 0.013 0.019 0.028 0.043 8 256 0.042 0.057 0.087 0.148 9 512 0.118 0.174 0.302 0.559 10 1024 0.354 0.606 1.124 2.279 11 2048 1.220 2.242 4.545 10.041 12 4096 4.517 8.982 19.706 41.568 13 8192 17.864 39.063 82.814 169.274 14 16384 78.454 165.893 337.830 718.843
function test(n, s) { var res = ''; for (var q=0; q


Ответ

Почему так происходит?
Судя по сорцам V8, строки оптимизированы для конкатенации. При конкатенации вместо создания новой строки создаётся экземпляр, который ссылается на конкатенируемые куски. Каждый кусок тоже может ссылаться на подкуски. Таким образом строится бинарное дерево.
// The ConsString class describes string values built by using the // addition operator on strings. A ConsString is a pair where the // first and second components are pointers to other string values. // One or both components of a ConsString can be pointers to other // ConsStrings, creating a binary tree of ConsStrings where the leaves // are non-ConsString string values. The string value represented by // a ConsString can be obtained by concatenating the leaf string // values in a left-to-right depth-first traversal of the tree. class ConsString : public String { public: // First string of the cons cell. inline String* first(); inline void set_first(String* first, WriteBarrierMode mode = UPDATE_WRITE_BARRIER); // Second string of the cons cell. inline String* second(); inline void set_second(String* second, WriteBarrierMode mode = UPDATE_WRITE_BARRIER); // Minimum length for a cons string. static const int kMinLength = 13; // ... }
Также есть подклассы строк для подстрок, явно разделяются однобайтовые и двубайтовые строки, и так далее.
// The Sliced String class describes strings that are substrings of another // sequential string. The motivation is to save time and memory when creating // a substring. A Sliced String is described as a pointer to the parent, // the offset from the start of the parent string and the length. Using // a Sliced String therefore requires unpacking of the parent string and // adding the offset to the start address. A substring of a Sliced String // are not nested since the double indirection is simplified when creating // such a substring. // Currently missing features are: // - handling externalized parent strings // - external strings as parent // - truncating sliced string to enable otherwise unneeded parent to be GC'ed. class SlicedString : public String { public: inline String* parent(); inline void set_parent(String* parent, WriteBarrierMode mode = UPDATE_WRITE_BARRIER); inline int offset() const; inline void set_offset(int offset); // Minimum length for a sliced string. static const int kMinLength = 13; // ... }
В результате такая простая операция как конкатенация превращается в подобное
MaybeHandle Factory::NewConsString(Handle left, Handle right) { if (left->IsThinString()) { left = handle(Handle::cast(left)->actual(), isolate()); } if (right->IsThinString()) { right = handle(Handle::cast(right)->actual(), isolate()); } int left_length = left->length(); if (left_length == 0) return right; int right_length = right->length(); if (right_length == 0) return left;
int length = left_length + right_length;
if (length == 2) { uint16_t c1 = left->Get(0); uint16_t c2 = right->Get(0); return MakeOrFindTwoCharacterString(isolate(), c1, c2); }
// Make sure that an out of memory exception is thrown if the length // of the new cons string is too large. if (length > String::kMaxLength || length < 0) { THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String); }
bool left_is_one_byte = left->IsOneByteRepresentation(); bool right_is_one_byte = right->IsOneByteRepresentation(); bool is_one_byte = left_is_one_byte && right_is_one_byte; bool is_one_byte_data_in_two_byte_string = false; if (!is_one_byte) { // At least one of the strings uses two-byte representation so we // can't use the fast case code for short one-byte strings below, but // we can try to save memory if all chars actually fit in one-byte. is_one_byte_data_in_two_byte_string = left->HasOnlyOneByteChars() && right->HasOnlyOneByteChars(); if (is_one_byte_data_in_two_byte_string) { isolate()->counters()->string_add_runtime_ext_to_one_byte()->Increment(); } }
// If the resulting string is small make a flat string. if (length < ConsString::kMinLength) { // Note that neither of the two inputs can be a slice because: STATIC_ASSERT(ConsString::kMinLength <= SlicedString::kMinLength); DCHECK(left->IsFlat()); DCHECK(right->IsFlat());
STATIC_ASSERT(ConsString::kMinLength <= String::kMaxLength); if (is_one_byte) { Handle result = NewRawOneByteString(length).ToHandleChecked(); DisallowHeapAllocation no_gc; uint8_t* dest = result->GetChars(); // Copy left part. const uint8_t* src = left->IsExternalString() ? Handle::cast(left)->GetChars() : Handle::cast(left)->GetChars(); for (int i = 0; i < left_length; i++) *dest++ = src[i]; // Copy right part. src = right->IsExternalString() ? Handle::cast(right)->GetChars() : Handle::cast(right)->GetChars(); for (int i = 0; i < right_length; i++) *dest++ = src[i]; return result; }
return (is_one_byte_data_in_two_byte_string) ? ConcatStringContent( NewRawOneByteString(length).ToHandleChecked(), left, right) : ConcatStringContent( NewRawTwoByteString(length).ToHandleChecked(), left, right); }
bool one_byte = (is_one_byte || is_one_byte_data_in_two_byte_string); return NewConsString(left, right, length, one_byte); }
Всякие подобные премудрости — всегда компромисс, обложенный эвристиками. Движок JavaScript пытается угадать, когда выгоднее использовать какой способ: когда создавать полноценную строку, когда строить бинарное дерево строк; когда важнее скорость конкатенации, когда важнее скорость итерации; и так далее.
Очевиден плюс: потенциально очень медленные операции становятся более быстрыми. Особенно важно это для случаев, когда некоторые паттерны использования классов часто используются программистами, например, конкатенация строк. Но очевиден и минус: другие операции становятся медленнее, кроме того движок не всегда угадывает намерения программиста и выбирает самый оптимальный способ.
Почему так сделано в JavaScript?
Потому что язык сделан намеренно простым для понимания и использования. Чем больше магии, чем меньше нужно думать о внутреннем представлении, тем проще писать программы.
Кроме того, когда язык создавался, скорость выполнения вовсе не имела значения, потому что сложных программ на языке никто не писал. Сложные программы появились намного позже. Если вы запустите эту программу не в Chrome последней версии, а в любом браузере 20-летней давности, вы обнаружите совсем другую производительность.
Почему так не сделано в других языках?
Это зависит от языка и его предназначения. Например, строки в C++ простые и топорные, от программиста требуется указывать каждое движение, появляется разница в производительности между объявлением строки в теле цикла или вне, между передачей строки в функцию по ссылке и по значению, от программиста ожидается указание заранее ожидаемой длины строки и так далее.
С другой стороны, в PHP массивы находятся в роли строк в JS и дадут фору по сложности. Массивы могут быть векторами, словарями, множествами и так далее. Они обладают сложной структурой, которая оптимизирована под каждый случай.
В целом, подход к стандартным классам меняется от языка к языку и даже между версиями одного компилятора языка. Сегодня в JavaScript и PHP появляются типизированные массивы, завтра может появиться StringBuilder.

Что такое HTML5 WebSocket, Long/short Polling, AJAX, WebRTC, Server-Sent Events?

Я тут решил сделать велосипед: небольшой чат для друзей. Мне стало интересно, а как это происходит наподобие вконтакте или других проектах? Ведь сообщения/уведомления приходят мгновенно и не приходится пользователю вручную обновлять или принудительно через код рефрешить страницу.
Я искал и наткнулся на то, что раньше популярно было делать так: Ajax шлет на сервер запрос через равные короткие промежутки времени для получения ответа.... А еще есть long/short polling. В начале заприметил их, но в процессе поиска наткнулся на HTML5 websockets. Кажется он легок в реализации.
Все, в той или иной мере делают одну вещь, но мне непонятно в чем же их различие? Как они работают? Хотелось бы знать хотя бы вкратце как они работают, чтоб можно было использовать ту или иную технику в зависимости от ситуации. Или перейти на HTML5 WS и не париться?


Ответ

WebSockets, по сути, новая технология. Long polling - некий обходной и грязноватый маневр предотвратить создание нового соединения на каждый новый запрос, как это делает AJAX. Но ведь long polling был создан тогда, когда WebSockets не существовало вовсе. А технологии развиваются, эволюционируют и вот сейчас популярность набирают уже они.
Моя небольшая отсебятинка-рекомендация: почитать и ознакомиться именно с WebSockets
Но, тем не менее.
как работают различные методы связи в Интернете:
AJAX - запрос → ответ. Создает соединение с сервером, отсылает заголовки запроса с некоторыми данными, получает ответ с сервера, закрывает соединение. Запросы отправляются часто. Поддерживается во всех основных браузерах
Как плюс: просто реализовать, данные можно сжать.
Как минус: слишком много ненужных запросов (это наверное основной минус :-)). А также большие задержки между созданием и получением данных. Сервер отсылает данные не тогда, когда они появились, а когда прилетит новый запрос. Long poll - запрос → ожидание → ответ. Создает соедениение с сервером, как AJAX, но оставляет соединение открытым некоторое время (не сильно большое). Сервер НЕ реагирует на запрошенную информацию и ждет, пока не появится новой информации. Пока соединение открыто - клиент ждет данные с сервера. Как только что-то новое появилось у сервера - он отсылает ее клиенту. Клиент получив новую информацию или если истекло время ожидания НЕМЕДЛЕННО отсылает другой запрос серверу, запуская процесс ожидания на нем снова. Как правило, соединение обычно переустанавливают раз в 20-30 секунд (у вконтакте например 29 секунд), чтобы избежать возможных проблем, например с HTTP-прокси. Поддерживается во всех основных браузерах
Как плюс: малое количество запросов (по сравнению с обычным AJAX то) WebSockets - клиент ↔ сервер. Создается TCP соединение с сервером и сохраняется открытым столько, сколько требуется. Сервер или клиент могут легко закрыть его. При подключении происходит так называемое "рукопожатие", т.е. клиентом отсылаются специальные заголовки, которые шифруется base64. Если серверу всё понравилось, то он вернет заголовок Accept. Следует помнить, что клиентское рукопожатие всегда будет иметь заголовок Origin, который будет отправлен на сервер, хотят они принимать клиентов с различных источников или нет. После установки соединения сервер и клиент могут посылать друг другу сообщения, когда новая информация доступна (либо на сервере, либо на клиенте) - в обоих направлениях в любое время. Это очень эффективно, если приложение требует частого обмена данными в обоих направлениях. Также данные, посланные с клиента на сервер в некоторой степени шифруются. Еще один плюс. [чем поддерживается] (уже неплохо развит) WebRTC (Web Real Time Communication - веб-коммуникация в режиме реального времени) - peer ↔ peer (P2P — равный к равному). Транспорт для установления связи между клиентами (двумя и более), через который могут передаваться обычные данные и медиапотоки. Используются UDP, TCP и даже более абстрактные слои. Как правило это используется для передачи данных большого объема например медиаданные (аудио и видео). Обе стороны (пиры) могут передавать данные друг другу независимо друг от друга. [чем поддерживается] (средняя поддержка) Server-Sent Events - клиент ← сервер. Клиент устанавливает постоянное и длительное соединение с сервером. Отсылать данные может только сервер к клиенту. Если клиент хочет что-то послать на сервер, то для этого придется использовать другую технологию/протокол. Этот протокол HTTP прост в реализации на большинстве серверных платформах. Это более предпочтительный протокол для использования, нежели Long Polling. [чем поддерживается] (IE как всегда в отстающих)
Преимущества:
У каждого способа есть свои плюсы и свои минусы. Однако, сейчас хорошим и перспективным считается WebSockets. Основное преимущество WebSockets для сервера, является то, что это не запрос HTTP, а собственно протокол связи на основе сообщений. Это позволяет достичь огромные преимущества производительности и архитектуры. Например, в node.js можно разделить одну и ту же память для различных соединений сокета, таким образом получить доступ к общим переменным. Так что вам не нужно использовать базу данных в качестве обменного пункта в середине (как с помощью AJAX или Long Polling и, например, PHP). Вы можете хранить данные в оперативной памяти...
Решать что использовать, конечно, только вам!

Ответ переведен, слегка изменен и дополнен отсебятиной с enSO

Что значит * (звёздочка) и ** двойная звёздочка в Питоне?

К примеру, что делает * (астериск) в следующем коде:
print(*min(p for p in counter.items() if p[1] == max_count))
print(*team, *coef)
seasons = [datetime(*args) for args in [ (Y, 1, 1), # winter (Y, 3, 1), # spring (Y, 6, 1), # summer (Y, 9, 1), # autumn (Y, 12, 1) # winter ]]
def lcm(*args): """Least common multiple.""" return reduce(lambda a, b: a * b // gcd(a, b), args)
async def watchdog(loop, last_activity_time, timeout, func, *args): "Run *func(*args)* if more than *timeout* seconds since *last_activity_time*." while (loop.time() - last_activity_time()) < timeout: await asyncio.sleep(1) return func(*args)
и что делают две ** звёздочки:
'{a:.{n}f}'.format(**vars())
class A: def __init__(self, a, **kwargs): super().__init__(**kwargs)


Ответ

Звёздочка в Питоне помимо умножения x*y (help('*')) и возведения в степень x**y (help('**'))† используется, чтобы обозначить ноль или более чего-либо.
К примеру в описании параметров функции:
def f(*args): print(args)
* означает, что функция принимает ноль или более аргументов, которые доступны внутри функции в виде кортежа args
>>> f(1,'a') (1, 'a')
Для именованных параметров используются две звёздочки
def g(a, b, *args, name='default', **kwargs): print(a, b, args, name, kwargs)
здесь g() принимает два обязательных аргумента и произвольное (ноль или более) количество других аргументов:
>>> g(1, b=2, c=3) 1 2 () default {'c': 3}
kwargs—это словарь дополнительных аргументов, переданных по имени (c в данном случае). А args это пустой кортеж (), так как дополнительных позиционных аргументов не было передано.
После * все параметры обязаны передаваться по имени, пример
def min_item(items, *, key=lambda x: x): ...
При вызове, если задан, key обязан быть указан по имени: min([1,2,-3], key=abs)
Принятие произвольного количества аргументов может быть полезно при создании функций-обёрток:
def my_print(*args, **kwargs): flush = kwargs.pop('flush', True) # flush unless overriden print(*args, flush=flush, **kwargs)
При множественном наследовании **kwargs помогает реализовать требование совместимости параметров для методов базовых классов, так как kwargs позволяет передать произвольные именованные аргументы.
Видно, что звёздочку можно использовать и при вызове функции
L = [1, 2, 3] s = "abc" print(*L, *s) # iterable unpacking: print(1, 2, 3, 'a', 'b', 'c') # -> 1 2 3 a b c
произвольные коллекции (iterable в общем случае) L, s распаковываются и каждый их элемент передаётся в виде отдельного агрумента в вызываемую функцию (print()).
Можно использовать и при явном присваивании:
>>> first, *middle, last = L >>> first, middle, last (1, [2], 3)
в этом случае первый и последний аргументы из списка L распаковываются в явно приведённые имена (first, last), а остаток ноль или более элементов в виде списка помещаются в middle
Звёздочку можно использовать и при задании списков, кортежей, наборов и словарей в исходном коде, используя соответствующий синтаксис (tuple, list, set, and dictionary displays):
>>> *range(4), 4 (0, 1, 2, 3, 4) >>> [*range(4), 4] [0, 1, 2, 3, 4] >>> {*range(4), 4} {0, 1, 2, 3, 4} >>> {'x': 1, **{'y': 2}} # dictionary unpacking inside dictionary display {'x': 1, 'y': 2}
Тонкий момент: запятая в Питоне создаёт кортеж—скобки нужны только для пустого кортежа (). Поэтому первая строчка равнозначна: (*range(4), 4)
Так же как и при вызове функций, звёздочка распаковывает коллекцию здесь и действует как будто каждый элемент был передан отдельно в соответствующие конструкторы.
Таким образом можно сложить два словаря или произвольные отображения (Mapping):
>>> a = {'a': 1, 'b': 2} >>> b = {'a': 3, 'c': 0} >>> {**a, **b} {'a': 3, 'b': 2, 'c': 0} >>> {**b, **a} {'a': 1, 'c': 0, 'b': 2}
При наличии дублирующих ключей, более поздние значения побеждают, как обычно: {'a': 1, 'a': 3} == {'a': 3}
Знание, что делает звёздочка полезно, чтобы объяснить как zip(*matrix) транспонирует квадратную матрицу или как обойти итератор по ровно n элементов за раз: zip(*[iterator]*n)
Помимо указанных значений, звёздочка может присутствовать в имени файла для создания шаблона (wildcard), к примеру
from pathlib import Path
print(*Path().glob('*.py')) print(*Path().glob('**/*.py'))
Первый print() печатает через пробел все (ноль или более) имена файлов в текущей директории с расширением .py. Подобный (*.py) синтаксис популярен, так как он используется в командной строке (shell). Второй print() с двумя звёздочками ('**/*.py') выводит имена Питон-файлов во всем дереве директорий (включая вложенные директории).
В регулярных выражениях * означает повторение ноль или более раз:
import re
if re.fullmatch(r'x*', text): print('текст пустой или содержит только `x`')

† умножение натуральных чисел n*m можно рассматривать как повторение сложения (ноль или более) раз. Аналогично, возведение в степень n**m можно рассматривать как повторение умножения:
2 * 3 == 2 + 2 + 2 2 ** 3 == 2 * 2 * 2 [2] * 3 == [2, 2, 2]

Как сделать выравнивание текста по наклонной линии?

Даже примера кода нету : так как в голову не приходит как сделать

Гуру CSS3 как такое реализовать ?
Источник
Нашёл интересный вариант решение Пример
Вопрос задал на SO
Решение нашлось на SO
.element{ shape-outside: url(https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Triangle-grey.svg/2000px-Triangle-grey.svg.png); shape-image-threshold: 0.2; float: left; height: 300px; padding: 0 20px; } JS Bin

CSS shapes / dLotus

Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…Lorem ipsum…


Это круто ARPIL GOYAL выдал 100% результат


Ответ

Почти ничего сложного, если не бояться замарать ручки дикими костылями)
Вижу два варианта решения:
Использование shape-outside
Но, к сожалению, этот вариант абсолютно не кроссбраузерный и работает только в webkit-движках: http://caniuse.com/#feat=css-shapes
Куча блоков
В варианте с треугольным обтеканием это легко реализовать и автоматизировать процесс с помощью JS. Я считаю этот вариант вполне приемлемым, если того требует задача. Конечно, автоматизировать создание блоков в помощью js будет весьма затруднительно, если форма обтекания сложная.
В своем примере я использовал оба варианта.
С шейпами все ясно: по трем точкам нарисовал область обтекания и готово.
"Кучу блоков" я наплодил с помощью js: создал один блок шириной 300px и с помощью js наплодил после него потомков, каждый короче на 10px.
Пример на jsfiddle: https://jsfiddle.net/ipshenicyn/h2o9fbav/2/
HTML

очень много текста


CSS
Код треугольников обтекания:
.shape-left - это блок, который я буду плодить с помощью js
.shape-right - блок с использованием shape-outside
.shape-left{ float: left; width: 300px; height: 10px; clear: both; } .shape-right{ width: 400px; height: 400px; float: right; shape-outside: polygon(400px 0, 400px 400px, 0 400px); }
JS
Код, который множит .shape-left с убыванием ширины, создавая тем самым треугольную область:
var $el = $('.shape-left'), width = $el.width(), count = width / 10;
for(var i = 0; i < count; i++){ width = $el.width(); $el = $el.clone().css('width',width-10).insertAfter($el); }

Исчерпывающее руководство по form-based website authentication

Form-based authentication для веб-сайтов
Мы верим, что Stack Overflow должен быть не просто ресурсом для очень узких технических вопросов, но также содержать общие рекомендации о том, как решить множество общих проблем. "Form-based authentication для веб-сайтов" должно стать хорошей темой для подобного эксперимента.
Оно должно включать следующие темы:
Как авторизоваться Как оставаться авторизованным Как хранить пароль Использование секретных вопросов Как организовать восстановление имени пользователя/пароля OpenID Галочка "Запомнить меня" Автозаполнение имени пользователя/пароля Секретные URL (публичные URL, защищённые дайджест-аутентификацией) Проверка силы пароля и многое другое о form-based website authentication...
Оно не должно включать такое, как:
Роли и авторизация Основы HTTP аутентификации
Пожалуйста, помогите нам
Предлагая подтемы Присылая хорошие статьи по теме Дополняя официальный ответ
Также вы можете ознакомиться с оригиналом вопроса на Stack Overflow


Ответ

Часть первая: как авторизоваться
Мы предполагаем, что вы уже знакомы с тем, как построить форму вида "логин+пароль", которая отправляет POST запрос на сторону сервера. Раздел ниже будет описывать паттерны аутентификации для практического применения и то, как избежать самые распространённые "подводные камни".
HTTPS или не HTTPS, вот в чём вопрос
Только если ваше соединение уже не защищено (что значит оно не туннелировано через HTTPS с использованием SSL/TLS), данные из вашей формы входа будут преданы в виде простого текста, что позволяет любому находящемуся на линии между вашим браузером и сервером подслушивать эти данные в момент передачи. Если вы передаёте любые нетривиальные данные, используйте HTTPS.
В сущности, единственный реальный способ защититься от перехвата / анализа пакетов во время авторизации — использование HTTPS или другой схемы, основанной на сертификации (например TLS), или на доказанных и протестированных схемах типа "вызов-ответ" (как протокол Диффи — Хеллмана). Любая другая схема может быть с лёгкостью обойдена атакующим.
Конечно, вы можете пойти другим путём и использовать какие-нибудь системы двухэтапной аутентификации (в т.ч. Google Authenticator, кодовую книгу или генератор RSA ключей). Если вы правильно их используете, то всё должно работать даже без защищенного соединения, но трудно представить себе разработчика, который станет использовать двухэтапную аутентификацию, но при этом не использует SSL.
(Не) используйте свои собственные криптографические JavaScript-решения
Взяв во внимание ненулевую стоимость и ощутимую сложность установки SSL сертификата на своём веб-сайте, некоторые разработчики предпринимают попытки создания собственных внутрибраузерных криптографических решений чтобы избежать передачи данных входа в явном виде по незащищённому каналу.
Несмотря на всю красоту идеи, по существу это бесполезно (и даже опасно для безопасности), если только данное решение не будет совмещено с вышеизложенными схемами, а именно: либо защита линии сильным шифрованием, либо использование проверенного временем механизма типа "вызов-ответ" (если вы не знаете, что это такое, просто знайте, что это одна из самых сложных с точки зрения задумки и воплощения в жизнь концепций цифровой безопасности).
Капча - враг человека
CAPTCHA призвана мешать исполнению одной конкретной категории атак: подбор по словарю (брутфорс) без участия человека. Вне всякого сомнения это настоящая угроза, однако есть более элегантные способы борьбы с ними и без применения капчи, но мы поговорим о них позднее.
Знайте, что реализации капчи различны; они часто нерешаемы человеком, многие из них неустойчивы против ботов, абсолютно все бесполезны против дешёвой рабочей силы стран третьего мира (по данным OWASP, цена приблизительно составляет $12 за 500 попыток), и некоторые из них могут быть противозаконны в определённых странах (см. OWASP Guide To Authentication). Если вам всё-таки необходимо использовать капчу, используйте reCAPTCHA, так как она по определению трудна в оптическом распознавании (используются уже нераспознанные сканы из книг) и старается изо всех сил быть user-friendly.
Лично я нахожу капчи раздражительными. Я использую их только в крайнем случае, когда пользователь превысил все мыслимые рубежи неудачных попыток ввода логина/пароля. Это происходит с приемлемой редкостью, и повышает безопасность в целом.
Хранение / проверка паролей
Вероятно, что после всех массовых взломов и утечек данных в сеть, которые мы наблюдали в последние годы, не для кого не секрет, но я повторюсь: не храните пароли в вашей базе данных незашифрованными. Базы данных пользователей взламываются, утекают или подбираются SQL-инъекциями, и если вы храните их в виде простого незашифрованного текста - это мгновенный game over всей вашей системе безопасности.
Но если вы не можете хранить пароль, то как же вы проверите, что комбинация "логин + пароль" верна? Ответ: хешировать используя функцию формирования ключа. Всякий раз, когда создаётся новый пользователь или изменяется его пароль, вы берёте пароль и прогоняете его через ФФК, такую как bcrypt, scrypt или PBKDF2, изменяя обычный текст пароля ("ХренВыМеняВзломаете666") в длинную хаотично выглядящую строку, которую куда более безопасно хранить в вашей базе данных. Чтобы проверить пароль на подлинность при попытке ввода, вы берёте введённый пользователем пароль и также прогоняете его через ФФК, в этот раз вставляя соль, и сравниваете полученный hash с тем, что храниться в базе данных. bcrypt и scrypt хранят хэшированный пароль уже с солью. Прочитайте данную статью (на англ.) на sec.stackexchange , если хотите копнуть глубже.
Причина, по которой мы используем соль, в том, что хэширования самого по себе не достаточно -- она необходима для защиты хэша от радужной таблицы. Соль предотвращает присваивание двух одинаковых паролей одному и тому же хэшу, что спасает от сканирования базы данных в один прогон, если атакующий пытается подобрать пароль.
Не следует использовать криптографический хэш для хранения паролей, так как выбираемые пользователями пароли не достаточно сильны (то есть обычно не содержат достаточно энтропии) и подбор пароля может быть выполнен в относительно небольшой промежуток времени атакующим, имеющим доступ к хэшам. Вот почему используются ФФК. Они значительно удлиняют ключ, то есть каждое угадывание пароля, которые производит атакующий, заставляет его использовать итерацию через алгоритм хэширования несколько раз (скажем 10000 раз), что делает атаку медленнее в 10000 раз.
Данные сессии - "Вы вошли как KyCoK_3a6oPa_123"
Как только сервер сравнил логин и пароль с записями базы данных и нашёл совпадение, системе нужен способ запомнить, что браузер был авторизован. Это факт должен храниться исключительно на стороне сервера.
Если вы не знакомы с тем, как работают данные сессии: одна случайно сгенерированная строка сохраняется как куки с датой истечения, затем используется как ссылка на набор данных - данные сессии, которые хранятся на сервере. Если вы используете MVC фреймворк - не волнуйтесь, об этом несомненно уже позаботились.
Если это возможно, убедитесь, что куки сессии имеют флаг безопасности и флаг HttpOnly перед тем, как отправлять их на сервер. Флаг HttpOnly даёт немного защиты против чтения путем межсайтового скриптинга. Флаг безопасности гарантирует, что куки будет отправлен только с помощью HTTPS, что защитит вас от перехвата пакетов. Значение куки должно быть непредсказуемым. Если куки начинает ссылаться на более несуществующую сессию, его значение должно быть изменено незамедлительно, что предотвратит session fixation
Часть вторая: как оставаться в системе или печально известная кнопка "Запомнить меня"
Постоянные Cookie-файлы (отвечающие за функцию "запомнить меня") находятся в зоне риска. С одной стороны они целиком и полностью безопасны, как и обычные логины, в случае, если пользователь понимает как с ними обращаться; с другой стороны, они могут представлять большую угрозу для безопасности у неопытных пользователей, которые могут использовать их на компьютерах в общественных местах и после забыть выйти из системы или вовсе не знать что такое файлы cookie и как их удалить.
Лично мне нравятся сохраненные логины на веб-сайтах, которые я регулярно посещаю, но я знаю как с ними обращаться. Если вы уверены, что ваши пользователи тоже в курсе, то можете с чистой совестью использовать сохраненные логины. Если нет - что ж, тогда можно философски заключить, что пользователи, которые легкомысленно относились к конфиденциальности своих учетных данных и были взломаны, виноваты сами. Конечно, мы также не ходим по домам пользователей и не срываем весь этот позор в виде приклеенных к мониторам листочков с логинами и паролями.
Разумеется, некоторые системы не могут позволить себе взлом ни одного аккаунта. Для них не стоит использовать сохраняемые логины.
Если вы все же решили использовать cookie-файлы для сохранения логинов, вот что вам следует сделать:
Во-первых, уделите немного времени на прочтение статьи от Paragon Initiative по этой теме. Вам нужно понять кучу разных вещей, а она отлично объяснит каждую. Просто напоминаем об одной из самых распространенных ошибок. НЕ ХРАНИТЕ КУКИ ЛОГИНА (ТОКЕН) В СВОЕЙ БАЗЕ ДАННЫХ, ХРАНИТЕ ТОЛЬКО ИХ ХЭШ. Сохраненный login token эквивалентен паролю, так что если руки хакеров дотянутся до вашей базы данных, они могут использовать token, также, как если бы у них была комбинация логина и пароля, поэтому используйте login token при сохранении. Поэтому используйте хэширование (согласно https://security.stackexchange.com/a/63438/5002 слабый хэш отлично служит этой цели) когда храните login tokens.
Часть третья: использование секретных вопросов
Не используйте "секретные вопросы". "Секретные вопросы" -- антипаттерн безопасности. Ознакомьтесь с документом по четвёртой ссылки из раздела ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ. Вы можете спросить Сару Пэйлин о том случае, когда её почтовый ящик на Yahoo! был взломан во время прошлой предвыборной кампании, потому что её секретный ответ был "Wasilla High School"!
Даже если пользователи сами вводят секретный вопрос, велика вероятность, что они выберут:
Вопрос из "стандартного набора", как девичье имя матери или имя любимого питомца Простая информация, которую может раздобыть любой желающий из их личного блога, профиля LinkedIn или тому подобного Любой вопрос, ответ на который получить перебором легче, чем подобрать пароль. А именно: любой вопрос, который вы только можете вообразить.
Вывод: секретные вопросы по существу не безопасны в фактически всех своих формах и проявлениях и не должны быть применены для аутентификации по любой причине.
Настоящая причина, почему секретные вопросы вообще существуют в том, что они так удобно экономят деньги на звонках в тех. поддержку с просьбой выслать код реактивации. Это цена безопасности и репутации Сары Пэйлин. Стоит того? Уверен, что нет.
Часть четвёртая: восстановление забытого пароля
Я уже объяснял, почему вы не должны использовать секретные вопросы для восстановления забытых/утраченных пользователем паролей; также, безусловно, не стоит отправлять пользователям их пароли по электронной почте. Есть по крайней мере две широко распространенных уловки, чтобы избежать этого.
Не менять забытый пароль на надежный автогенерируемый, - подобные пароли сложно запомнить, а это значит, что пользователь или изменит его, или напишет на желтом стикере и приклеит внизу монитора. Так что вместо того, чтобы устанавливать новый пароль, позвольте пользователям выбрать, - что они и хотят сделать. Всегда хешируйте пароль/token в базе данных. Опять же, это другой пример эквивалента пароля, так что он ДОЛЖЕН быть хеширован на случай, если хакер доберется до вашей базы. Когда потребуется утраченный пароль, оправьте пароль в виде просто текста на электронную почту пользователя, затем хешируйте и сохраните в базе данных, -- и затем избавьтесь от оригинала.
Последнее замечание: будьте уверены, что ваш интерфейс для ввода "забытого пароля" настолько же безопасен, насколько и ваша форма входа. Хакер просто использует ее, вместо того, чтобы получить доступ. Убедитесь, что генерируете очень длинный "забытый пароль" (например 16 символов разного регистра) это хорошо для начала, но не забудьте добавить схемы защиты, используемые в форме входа.
Часть пятая: проверка силы пароля
Во-первых, вам нужно прочитать эту маленькую статью, чтобы узнать реальное положение вещей: 500 самых распространённых паролей (на англ.)
Ладно, быть может это и не самый каноничный лист самых распространённых паролей во всех системах везде за всё время, но он хорошо демонстрирует нам то, как безосновательно пользователи подходят к выбору пароля, если к ним не предъявлены требования. Плюс, этот лист до боли похож на лист паролей, полученных в результате анализа недавно украденных аккаунтов.
Итак: без требований к минимальной длине пароля, 2% пользователей используют один из топ-20 самых популярных паролей. Вывод: если у хакера будет словарь из всего лишь 20 паролей, каждый 50-ый аккаунт на вашем веб-сайте будет взломан.
Избежать этого можно установив на сайте минимальный порог энтропии пароля. Специальная публикация национального института стандартов и технологий содержит набор очень хороших предложений. Так, скомбинировав словарь и анализ раскладки клавиатуры, можно отбросить 99% всех ненадёжных паролей на уровне 18-ти битной энтропии. Просто высчитывание силы пароля и вывод визуального индикатора силы пароля -- это хорошая практика, но недостаточная. Если пользователя не заставить, он скорее всего проигнорирует рекомендации.
Рекомендуем посмотреть данный комикс, чтобы отбросить стереотип о трудной запоминаемости паролей с высокой энтропией.
Часть шестая: все больше и больше - или предотвращение скоропалительных попыток входа
Для начала давайте ознакомимся с цифрами
Если нет времени на ознакомление с данными статьи, вот краткая информация:
Практически моментально можно взломать слабый пароль, даже если взламываете его при помощи счёт Практически моментально можно взломать буквенно-цифровой 9-ти символьный пароль, если он не чувствителен к регистру знаков Практически моментально можно взломать сложный символьно-буквенно-цифровой, верхне- и нижнерегистровый, если в его составе менее 8-ми символов (обычный компьютер может подобрать пароль, состоящий из 7 символов за считанные дни или даже часы) Однако придется потратить уйму времени, чтобы взломать даже шестизначный пароль, если у вас стоит ограничение на одну попытку в секунду
Так что же мы уяснили из этих цифр? Ладно, много, но выделим главное: не так уж сложно защититься от быстрых бесконечных попыток войти в систему (так называемых брут-форс). Но все не так просто, как кажется.
Короче говоря, есть три варианта эффективной защиты от брут-форса (и атаки по словарю, но учитывая, что вы уже используете политику сильных паролей, это больше не проблема):
Наличие капчи после N-го числа неудачных попыток (адски бесит и зачастую неэффективно - но я повторяюсь) Блокировка аккаунта и требование подтверждения адреса электронной почты после N-го числа неудачных попыток (DoS-атака только и ждёт, чтобы это случилось) И, наконец, защита логина -- то есть установка временного промежутка между попытками ввода пароля после N неудачных попыток (да, DoS атаки всё ещё возможны, но происходить они будут гораздо реже и с ними будет легче справиться)
Лучшая практика №1: короткие промежутки времени, увеличивающиеся с ростом числа неудачных попыток входа, например:
1 неудачная попытка - нет задержки 2 неудачные попытки - 4 секунды 3 неудачные попытки - 8 секунд 4 неудачные попытки - 16 секунд и так далее
Для DoS атаки такая система очень неудобна, так как каждый последующий промежуток времени блокировки больше всех предыдущих вместе взятых.
Уточняем. Задержка - это не задержка перед возвратом ответа браузера. Это больше похоже на таймаут или рефактерный период, в течение которого попытки войти в какой-либо аккаунт или с определенного IP- адреса вовсе не принимаются. То есть правильные credentials или как их там, не вернутся я так понимаю после успешного входа в систему. и неверные кредентиалы не увеличат задержку.
Лучшая практика №2: среднесрочная задержка, которая наступает после N неудачных попыток, например:
1-4 неудачные попытки - нет задержки 5 попыток - 15-30 минут задержка
Для DoS взломать эту схему сложно, но, безусловно, выполнимо. Также стоит отметить, что такая задержка раздражает авторизованных пользователей. особо забывчивые вас возненавидят.
Лучшая практика №3: совмещение этих двух подходов, либо фиксированное короткое время задержки, наступающее после N числа неудачных попыток, например: 1-4 неудачных попытки - нет задержки 5+ - 20 секунд задержки или увеличение времени задержки с ростом числа неудачных попыток входа, например:
1 неудачная попытка - 5 секунд 2 неудачные попытки - 15 секунд 3+ неудачные попытки - 45 секунд
Эта последняя схема была заимствована из списка лучших предложений OWASP (ссылка 1 из списка "ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ") и должна рассматриваться как лучшая практика.
По правилу большого пальца, я бы сказал, что чем надежнее ваш пароль, тем меньше вам придётся мучить пользователей задержками. Если вы требуете надежный (с учетом регистра символов + требование наличия букв и цифр) 9+ символов в пароле, можно предоставить пользователям 2-4 попытки ввода пароля до включения блокировки.
Для DoS взломать такую систему будет очень не просто. И наконец, всегда разрешайте сохраняемые (куки) логины (и/или форму, защищённую капчей) для входа, так у авторизованных пользователей не будет задержек, пока продолжается атака. Таким образом для DoS задача из очень сложной превращается в экстремально сложную.
Кроме того есть смысл делать более совершенную защиту для аккаунта администратора, так как те являются наиболее привлекательными для взломщиков.
Часть седьмая: распределённая брут-форс атака
Попутно отметим, что более продвинутые атакующие будут пытаться обойти задержку логина при помощи "распределения активности":
Распределять попытки ботнетом во избежание маркировки IP Вместо того, чтобы брать одно пользователя и пробовать на нём 50 000 самых распространённых паролей (чего они не могут из-за задержек), они возьмут самый распространённый пароль и попробуют его на 50 000 пользователей. Таким образом, они не приблизятся к лимиту неудачных вводов логина / пароля и их шансы возрастут, так как самый популярный пароль намного легче встретить, чем пароль номер 49 995. Разделят запросы к каждому логину интервалом в 30 секунд, чтобы не попадаться на радары.
Так что лучшей практикой будет учитывать суммарное число неудачных попыток входа во всей системе и использовать среднее их число как лимит для каждого отдельного пользователя.
Слишком абстрактно? Позвольте перефразировать:
Скажем, ваш сайт имеет средний показатель 120 неудачных входов в день за последние 3 месяца. Взяв этот период за основу вы можете умножить глобальный лимит на 3 -- т.е. 360 неудач в день. Теперь, если если это пороговое значение будет превышено (а лучше мониторьте изменение среднего показателя), активируйте всесистемную задержку входа для ВСЕХ пользователей (опять же, исключая вход с куки и вход с капчей).
Также ознакомьтесь с вопросом на SO, включающим больше информации о том, как обойти подводные камни в борьбе с распределёнными брут-форсами.
Часть восьмая: двухфакторная аутентификация и провайдеры аутентификации
Credentials могут быть скомпрометированы либо эксплоитами, либо их записью и утратой носителя, кражей ноутбука, а могут быть введены на фишинговом сайте. Логины могут быть дополнительно защищены двухфакторной аутентификацией, которая использует другие методы, такие, как одноразовые коды, полученные чрез телефонный звонок, SMS сообщения, приложения или электронный ключ. Некоторые провайдеры предлагают двухфакторную систему.
Аутентификация может быть возложена на single-sign-on сервис, где другие провайдеры собирают credentials. Это переправляет проблему доверенной третьей стороне. Google, Yandex и Twitter предоставляют стандартный набор SSo услуг, а FaceBook предоставляет схожее запатентованное решение.
ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ о веб-аутентификации (на англ.)
OWASP Guide To Authentication / OWASP Authentication Cheat Sheet Dos and Don’ts of Client Authentication on the Web (very readable MIT research paper) Wikipedia: HTTP cookie Personal knowledge questions for fallback authentication: Security questions in the era of Facebook (very readable Berkeley research paper)

Как объявить массив лямбд

Заинтересовал такой теоретический вопрос - а как объявить массив лямбд? И можно ли это сделать в принципе? Например, объявляем массив, потом в цикле заполняем его лямбдами, например, с захватом разных параметров, или еще как... Или, скажем, vector<...> - как его заполнить лямбдами?
Откровенно говоря, никогда не вдавался в такие детали, auto хватало с головой...


Ответ

Можно воспользоваться шаблоном std::function. Но все лямбды должны быть с одинаковой сигнатурой.
std::vector> lambdas;
lambdas.push_back([](){ return 1; }); lambdas.push_back([](){ return 2; }); /* ... */
Если самому сигнатуру писать лениво, то можно заставить компилятор подставлять её автоматически:
auto lambda1 = [](){ return 1; }; std::vector lambdas;
lambdas.push_back(lambda1 ); lambdas.push_back([](){ return 2; });
Поскольку захват переменных не оказывает влияния на сигнатуру лямбды, то можно заполнить массив вроде бы совершенно разными лямбдами:
int i0, i1;
std::vector> lambdas;
lambdas.push_back([](){ return -1; }); lambdas.push_back([=](){ return i0 + i1; }); lambdas.push_back([i0](){ return i0; }); lambdas.push_back([&i0](){ return i0++; }); lambdas.push_back([&](){ return i0++ + i1++; }); lambdas.push_back([&i0,i1](){ return i0++ + i1; }); /* и т.д. */

Надо ли явно закрывать приложение?

Все мы знаем жизненный цикл Activity, основного кирпичика, из которого состоит Android приложение (по крайней мере обладающее UI).
Также мы знаем, что Activity может уничтожаться самой осью в зависимости от нагрузки/наличия памяти и проч. обстоятельств.
Бывают такие приложения (например приложения онлайн банкинга), которые требуют авторизации/входа, в этом случае, по аналогии с обычными десктоп приложениями напрашивается опция/кнопка/экшн - Выход/Exit/Quit, нажатие на которое приводит к гарантированному закрытию приложения. Например, такой паттерн используется в широко известном мобильном приложении "Сбербанк Онлайн".
Другое чуть менее известное мобильное приложение от банка "Тинькофф", не использует такой шаблон (вернее таки там можно выйти из приложения таким способом, но это запрятано довольно глубоко), выход из приложения, в этом случае производится через системную кнопку back/назад - при достижении дна стека последних Activity - приложение само прекращает работу.
Внимание, вопрос: является ли использование специализированного действия выход/exit для Android приложений нормальным или это антипаттерн?


Ответ

Кроме объектов Activity, есть еще объект Application, который по задумке авторов ОС в идеале должен жить вечно для приложения, единожды запущенного пользователем. И нет никакого "легального" программного способа уничтожить этот объект. Так что такого понятия "Выйти", как мы привыкли на компьютерах, нет вообще.
Что касается приложений для онлайн-банкинга и других, работающих с закрытыми данными, тут должно быть четкое и ясное для пользователя разграничение на зоны (открытая\защищенная) и явный переход между ними.
Вход в защищенную зону с помощью ПИНа или логина-пароля, соответственно выход из нее по подтверждению (закончить? да-нет, либо явная команда "Выйти").
Само приложение может оставаться в памяти и даже на экране, главное чтобы все токены были удалены\отозваны, сессии закрыты и т.п. Это достаточный уровень безопасности для стандартного пользователя.
Если же ваши пользователи настолько параноидальны либо требовательны, что мол в памяти что-то осталось и т.п., то тут они ошиблись системой. Виртуальная машина не предоставляет настолько высокий уровень безопасности. И заморачиваться этим в приложении нет никакого смысла.

В чём смысл и преимущества #!/usr/bin/env?

Этак с начала времён (юниксовых, т.е. 1.01.1970) в начале скрипта рекомендовалось использовать shebang / hashbang - строку, указывающую на используемый интерпретатор, например:
#!/bin/bash echo 'hello world'
#!/usr/bin/python print 'hello world'
#!/usr/bin/python3 print('hello world')
Недавно наткнулся на рекомендацию использовать вместо этого такую форму:
#!/usr/bin/env bash echo 'hello world'
#!/usr/bin/env python print 'hello world'
#!/usr/bin/env python3 print('hello world')
Пожалуйста, объясните, как это работает и в чём преимущества такого подхода? Если есть ограничения и/или недостатки по сравнению с обычным способом - то и о них хотелось бы услышать.


Ответ

Основная идея - улучшение переносимости. Не гарантируется, что на различных системах исполняемый файл будет лежать по пути, который указан в shebang.
Использование env позволяет снизить этот риск за счет запуска команды на основе данных из переменной среды PATH
Более того, если по каким-либо причинам вместо стандартного исполняемого файла пользователь хочет использовать свой, то ему достаточно добавить путь к этому файлу в PATH без необходимости исправления скриптов:
~ $ cp /bin/bash /home/soon/python ~ $ export PATH=/home/soon:$PATH ~ $ env python [soon@archlinux ~]$ exit
В примере выше я скопировал bash к себе в домашнюю директорию (переименовав при этом файл в python), добавил путь в PATH и запустил python с помощью env, которая усужливо запустила bash, т.к. нашла его раньше.
Еще одним примером является использование виртуальных окружений при разработке на Python (virtualenv). Поскольку они также перебивают PATH, env позволяет использовать нужную версию исполняемого файла:
~ $ workon black-box-challenge-2016-py2 ~ (venv:black-box-challenge-2016-py2) $ env python
Python 2.7.11 (default, Mar 31 2016, 06:18:34) [GCC 5.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> print sys.executable /home/soon/.virtualenvs/black-box-challenge-2016-py2/bin/python >>>

Для чего нужна библиотека EventBus или её аналог Otto?

Просмотрел много ссылок в Google, но все равно не получил конкретного ответа: какую задачу решает библиотека Greenrobot EventBus (или её аналоги — Square Otto), чем она лучше стандартных вариантов и насколько она оправдывает свое использование?

Последний вопрос возникает из-за того, что я не представляю такую архитектуру, построенную на событиях. В MVP\RxJava библиотека совсем не вписывается (как минимум, у себя в проектах я не видел мест для их использования), разве что имеет смысл использовать библиотеку в общении между Service и другими частями приложения.


Ответ

Если отвлечься от всяких мудреных терминов, вроде событийно-ориентированного программирования, то данная библиотека служит для организации коммуникаций (обмена данными и событиями) между не связанными частями приложения. То есть, данная библиотека позволяет самым простым образом отправить произвольные данные из одной части приложения (например активити), в другую (например фрагмент). Работа основана на регистрации приемника события и затем отправки каких то событий "в эфир". Нужный приемник получит свой сигнал и вы можете принять отправленные данные. Пример:
Сначала создается класс-модель с конструкторами инициализации, которая будет хранить в себе передаваемые данные:
public class MessageEvent { public final String message;
public MessageEvent(String message) { this.message = message; } }
здесь класс-модель MessageEvent с одним полем данных типа String и конструктор инициализации этих данных. Вы можете создать произвольное количество полей данных произвольных объектов, а конструкторы инициализации могут включать несколько аргументов.
Затем в классе (например активити) из которого необходимо передать наши данные создается событие:
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
В классе, куда мы желаем отправить наши данные (например фрагмент) мы регистрируем приемник события:
@Override public void onStart() { super.onStart(); // регистрация приемника при старте фрагмента EventBus.getDefault().register(this); }
@Override public void onStop() { // отписываемся от регистрации при закрытии фрагмента EventBus.getDefault().unregister(this); super.onStop(); }
// В этом методе-колбэке мы получаем наши данные // (объект `event` типа класса-модели MessageEvent) public void onEvent(MessageEvent event){ // извлекаем из модели отправленную строку: event.message = "Hello everyone!" Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show(); }
Как только в активити будет выполнен код отправки события в колбэке onEvent() фрагмента появятся отправленные данные. Получат эти данные только те приемники, у которых в колбэке совпадет сигнатура с отправленным событием. Помимо классов-моделей можно передавать и простые типы данных, например:
Отправка:
int num = 5; EventBus.getDefault().post(num);
Получение (регистрация приемника опущена):
public void onEvent(int event){ Toast.makeText(getActivity(), "Получено число:" + event, Toast.LENGTH_SHORT).show(); }
Вообще, не важно, что вы шлете по шине: классы-модели сложной структуры, объекты, коллекции , массивы или примитивные типы - все это будет в целости доставлено получателю.
От инструментов обмена данными, которые реализует сама система Android, данное решение выгодно отличает простота и прозрачность использования, отсутствие какой либо связи между отправителем и получателем и возможность передавать данные любого типа и в любых количествах без всякой парцелибизации или сериализации.
Библиотека так же позволяет отправлять "липкие" события (не будут пропадать, пока не отправятся другие данные, например, при передаче между двумя активити, когда в момент передачи события приемника еще не существует, данные будут получены, когда получатель появится), работать с асинхронными потоками и тд. Смотрите документацию для подробностей.
Так же есть другие библиотеки аналогичной функциональности, например Square Otto Кроме того, шагнувшая намного дальше концепция реактивного программирования - фреймворк rxJava и дополнения для Android - фреймворк rxAndroid, учитывающий специфику платформы.

Почему отсортированный массив обрабатывается быстрее, чем не отсортированный?

Вот пример C++ кода, который выглядит очень странно. Почему-то, когда данные отсортированы код выполняется почти в шесть раз быстрее.
#include #include #include
int main() { // Заполнение данными const unsigned arraySize = 32768; int data[arraySize];
for (unsigned c = 0; c < arraySize; ++c) data[c] = std::rand() % 256;
// !!! С этой строкой следующий цикл работает быстрее std::sort(data, data + arraySize);
// Проверка clock_t start = clock(); long long sum = 0;
for (unsigned i = 0; i < 100000; ++i) { // Основной цикл for (unsigned c = 0; c < arraySize; ++c) { if (data[c] >= 128) sum += data[c]; } }
double elapsedTime = static_cast(clock() - start) / CLOCKS_PER_SEC;
std::cout << elapsedTime << std::endl; std::cout << "sum = " << sum << std::endl; }
Без std::sort(data, data + arraySize);, код выполняется 11.54 секунды. С отсортированными данными - 1.93 секунды.
Сначала, я думал что-то не так с языком или компилятором. Поэтому я попробовал использовать Java.
import java.util.Arrays; import java.util.Random;
public class Main { public static void main(String[] args) { // Заполнение данными int arraySize = 32768; int data[] = new int[arraySize];
Random rnd = new Random(0); for (int c = 0; c < arraySize; ++c) data[c] = rnd.nextInt() % 256;
// !!! С этой строкой следующий цикл работает быстрее Arrays.sort(data);
// Проверка long start = System.nanoTime(); long sum = 0;
for (int i = 0; i < 100000; ++i) { // Основной цикл for (int c = 0; c < arraySize; ++c) { if (data[c] >= 128) sum += data[c]; } }
System.out.println((System.nanoTime() - start) / 1000000000.0); System.out.println("sum = " + sum); } }
В итоге получились похожие результаты, но с меньшим разрывом.

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

Перевод вопроса Why is it faster to process a sorted array than an unsorted array?


Ответ

Перевод ответа: @Mysticial
Вы стали жертвой ошибки предсказателя переходов

Что такое Предсказание переходов?
Рассмотрим железнодорожный узел:
Картинка Mecanismo, из Wikimedia Commons. Используется под лицензией CC-By-SA 3.0
Теперь представим, что мы вернулись в XIX век – до изобретения радио.
Вы - оператор железнодорожного узла и слышите что прибывает поезд. У Вас нет идей по какому пути он должен идти. вы останавливаете поезд и спрашиваете машиниста куда ему нужно. И затем устанавливаете переключатель в нужное положение.
Поезда тяжелые и с большой инерцией. Поэтому начало движения и остановка занимают много времени.
Есть ли способ лучше? Вы можете угадать куда пойдет поезд!
Если угадали верно, поезд продолжит движение не останавливаясь. Если ошиблись, машинист остановит поезд, вернется назад, и наорет на Вас, чтобы вы перевели пути. Затем он может продолжить движение.
Если каждый раз угадывать правильно, поезд никогда не остановится. Если ошибаться очень часто, поезд будет терять много времени на остановку, возврат, и разгон.

Рассмотрим if-statement: на уровне процессора - это инструкция ветвления:

Вы процессор и вы видите ветвление. У Вас нет предположений какая ветка будет выбрана. Что Вам делать? Вы останавливаете выполнение и ждете завершение предыдущей инструкции. Затем вы продолжаете выполнение по правильному пути.
Современные процессоры сложны и имеют длинные конвейеры. Поэтому "разогрев" и "остановка" занимают много времени.
Есть ли способ лучше? Вы можете угадать какая ветка будет выполняться!
Если Вы угадали верно, выполнение продолжится. Если ошиблись, необходимо сбросить конвейер и откатиться к ветвлению. Затем можно продолжить с нужной ветки.
Если каждый раз угадывать правильно, выполнение никогда не будет останавливаться. Если ошибаться очень часто, будет тратиться много времени на остановку, откат и перезапуск.

Это предсказание переходов. Я признаю, это не лучшая аналогия, потому что поезд может указать направление подав сигнал флагом. Но в компьютерах, процессор не знает какое будет выбрано направление до самого последнего момента.
Так какую выбрать стратегию при угадывании. чтобы минимизировать количество раз. когда поезд должен возвращаться и идти по другому пути? Вы можете посмотреть историю! Если поезд идет влево в 99% случаев, тогда догадка будет: налево. Если чередуются, то и догадки тоже чередуются. Если один путь выбирается через каждые три раза, можно предположить то же самое...
Другими словами, вы пытаетесь определить шаблон поведения и следовать ему. Примерно так работает предсказатель переходов.
У большинства приложений хорошо определяемые ветвления. Поэтому у современных предсказателей переходов процент верных догадок обычно составляет >90%. Но когда они сталкиваются с непредсказуемыми ветвями с не определяемыми шаблонами, предсказатели переходов практически бесполезны.
Дальнейшее чтение: "Предсказатель переходов" статья на Wikipedia

Как указывалось выше, проблема в этом if-statement:
if (data[c] >= 128) sum += data[c];
Обратите внимание, что данные равномерно распределены в диапазоне от 0 до 255. Когда данные отсортированы, первая половина не будет заходить в if-statement. Затем, в if-statement будут заходить все оставшиеся.
Это очень хорошо для предсказателя, потому что много раз последовательно выбирается одна и та же ветка. Даже простой счетчик с насыщением правильно предскажет направление, за исключением случая после смены направления.
Быстрая визуализация:
T = ветка выбрана N = ветка не выбрана
data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ... branch = N N N N N ... N N T T T ... T T T ...
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (легко прогнозировать)
Но когда данные случайны, предсказатель бесполезен, потому что он не может предсказать случайные данные. Таким образом вероятность неверного прогнозирования может быть около 50%. (не лучше чем случайные догадки)
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ... branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...
= TTNTTTTNTNNTTTN ... (полностью случайно - тяжело прогнозировать)

Итак, что можно сделать?
Если компилятор не может оптимизировать выбор ветки, Вы можете попробовать использовать несколько хаков, если вы готовы пожертвовать читаемостью ради производительности.
Заменить:
if (data[c] >= 128) sum += data[c];
на:
int t = (data[c] - 128) >> 31; sum += ~t & data[c];
Это устраняет ветвление и заменяет его некоторыми битовыми операциями.
(Обратите внимание, что этот хак не совсем эквивалентен оригинальному условию. Но в данном случае он дает верный результат для всех входящих значений из data[].)
Benchmarks: Core i7 920 @ 3.5 GHz
C++ - Visual Studio 2010 - x64 Release
// Ветвление - случайно seconds = 11.777
// Ветвление - сортировано seconds = 2.352
// Без ветвления - случайно seconds = 2.564
// Без ветвления - сортировано seconds = 2.587
Java - Netbeans 7.1.1 JDK 7 - x64
// Ветвление - случайно seconds = 10.93293813
// Ветвление - сортировано seconds = 5.643797077
// Без ветвления - случайно seconds = 3.113581453
// Без ветвления - сортировано seconds = 3.186068823
Наблюдения:
С ветвлением: огромная разница между отсортированными и не отсортированными данными. С хаком: Нет разницы между сортированными и не сортированными данными. В случае C++, при использовании хака результаты немного медленнее, чем при использования ветвления на сортированных данных.
Общее правило заключается в избегании использования условий зависящих от данных в критических циклах. (как в этом примере)

Update :
GCC 4.6.1 с -O3 или -ftree-vectorize на x64 возможно генерировать CMOV инструкции. В этом случае нет разницы между сортированными или не отсортированными данными - оба случая быстрые. VC++ 2010 не доступно генерирование CMOV для ветвления даже при использовании /Ox Intel Compiler 11 делает что-то удивительное. Он меняет местами два цикла, тем самым поднимая непредсказуемое ветвление во внешний цикл. Так что становится не только не восприимчивым к ошибкам предсказания, но и в два раза быстрее чем VC++ и GCC могут сгенерировать! Другими словами, ICC воспользовался тестовыми циклами для победы над тестом... Если Вы даете Intel Compiler код без ветвления, он векторизует его... и это так же быстро, как и с ветвлением (со сменой циклов).
Это показывает, что даже зрелые компиляторы могут отличаться друг от друга в их способности оптимизировать код...