Pull to refresh

Comments 11

В итоге подход с параллельным выполнением методов всегда превосходил последовательный. В самом худшем раскладе параллельное выполнение на 40% быстрее. В самом лучшем - когда сошлись все звёзды - получалось 3х-кратное превосходство. Если взять средние показатели для целевого времени расчёта признаков (раннее утро), то параллельный подход выигрывает примерно в 2 раза.

В статье Cloudera немножко не про то говорят: там основная мысль в том, что бОльшее число партиций даст возможность разбросать таски по бОльшему числу экзекьюторов. В вашем случае прирост скорее всего в том, что в жирных подтасках утилизация экзекьюторов неравномерная и RM скорее всего успевал их забирать под нужды других задач. То есть тут прирост скорее всего только в том, что вы забрали ресурсы кластера под свои задачи и остальные стали работать чуть медленнее :)

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

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

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

Не понятно, почему вы считаете, что другие на кластере должны начать работать чуть медленнее. Приложение запросило и получило 100 ядер, Yarn скорее всего не отдаст эти ядра ни при последовательном, ни при параллельном выполнении.

И вот этот простой сводится к минимуму при параллельном запуске методов - почти в любой момент времени найдётся куда утилизировать временно освобождающиеся ядра.

Согласен, все так

Приложение запросило и получило 100 ядер, Yarn скорее всего не отдаст эти ядра ни при последовательном, ни при параллельном выполнении.

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

Да, именно этот эффект я и называю "эффективной утилизацией" ресурсов, что даже Yarn не способен по idle-таймауту забрать ядра ;) Но это лишь одна из составляющих, которая за счёт параллельности позволяет обеспечивать загрузку ядер.

Но, вообще говоря, в рамках этих экспериментов проводился ещё один, про который я в явном виде почему-то не написал. Параллельность даёт свой бурст, но отдельный прирост производительности получается, если в Spark настроить FAIR Scheduling, который между параллельными методами распределяет ресурсы почти поровну. Этот прирост воспроизводится и является достаточно существенным. И вот причину этого интересного эффекта внятно сформулировать не получается. Мои предположения в мыслях про нелинейность прироста производительности от количества ядер и более эффективном использовании этих ресурсов. [Но это моё объяснение может быть не точным]

Кажется, тут не сам Spark замешан, а что-то более общее - что-то из теории алгоритмов и моделей параллельных вычислений.

На одну таску дефолтно аллоцируется 1 cpu, поэтому число ядер на экзекьюторе в вашем кейсе может влиять только на то сколько тасок параллельно он исполняет внутри одной джобы.

Во-вторых, кажется, что для одной задачи прирост производительности от увеличения количества ядер не является линейным, а постепенно замедляется. Таким образом, большей эффективности получается добиться от параллельного запуска нескольких методов.

По умолчанию обычно одна таска = 1 ядро. Параллелизм на уровне экзекьютора зависит от параметров, которые вы указали в sparkConf.

Стоит отметить, что проведение экспериментов в высококонкурентной среде, коим является Hadoop-кластер, - это то ещё удовольствие. Каждый тестовый запуск не похож на предыдущий, т.к. постоянно кто-то ещё что-то считает. И мой i-ый тестовый запуск может получить ресурсов меньше/больше, чем i-1. Также скорость получения ресурсов неодинаковая: можно со старта получить 100 ядер, а можно эти 100 ядер добирать на протяжении долгого времени.

Для таких целей можно зафиксировать ресурсы за вашим приложением c помощью spark.executor.instances 

Да, зафиксировать-то, конечно, можно. Но приложение запустится, не дожидаясь пока Yarn аллоцирует для меня все ядра. Приложение запустится и на одном ядре, и в процессе будет "добирать". И если кластер в этот момент забит другими задачами, то моё приложение может достаточно долго на этом одном ядре работать. Это как повезёт ;)

Для тестов можно просто поставить нужное число initialExecutors и он не будет добирать

Возможно от этого сам старт приложения будет более долгим - пока там Yarn насобирает мне запрошенные ядра. Да и хотелось, чтобы тест был максимально приближен к реальному запуску. Однако идея интересная, нужно попробовать, спасибо!

Получилось ли проверить initialExecutors? Мне тоже интересно, можно ли перед стартом джоба "прогреть" кластер, чтобы он поднял необходимое кол-во нодов, если требуется.

Sign up to leave a comment.