войти зарегистрироваться

.NET whois

индекс
93,75

Parallel Extensions для .net 3.5

Aquafresh :-)Количество ядер у процессоров растет год от года. Но многие программы до сих пор умеют использовать только одно. В небольшой заметке хочу рассказать о дополнении к библиотеке System.Threading, которое называется Parallel Extensions. Это дополнение позволяет на высоком уровне выполнять задачи на всех доступных ядрах/процессорах.

Данная статья является лишь кратким вводным обзором в Parallel Extensions. Так же в конце статьи вы найдете ссылки на ресурсы, которые раскрывают тему во всех деталях.

Если интересно, то смело ныряем под кат.

Немного истории


Библиотека Parallel Extensions (PE) — совместный проект команды .net и Microsoft Research — впервые увидела свет 29 ноября 2007 года. Она создана для того, чтобы разработчики могли пользоваться современными многоядерными архитектурами, не утруждая себя трудоемким управлением потоками. Программы, написанные с применением библиотеки, автоматически используют все доступные ядра системы. Если же программа будет запущена на старом одноядерном компьютере, то выполнение будет происходить последовательно, практически без потерь в производительности. Таким образом, использование PE раскрывает все преимущества многоядерных технологий, сохраняя работоспособность на одноядерных системах.

Последнее обновление библиотеки было в июне 2008 года. Сейчас она имеет статус Community Technology Preview и, скорее всего, войдет в 4 версию .net.

Состав библиотеки


PE состоит из трех основных компонентов:
  • Task Parallel Library (TPL) — предоставляет такие императивные методы, как Parallel.For, Parallel.Foreach и Parallel.Invoke для выполнения параллельных вычислений. Вся работа по созданию и завершению потоков, в зависимости от имеющихся процессоров выполняется библиотекой автоматически.
  • Parallel LINQ (PLINQ) — надстройка над LINQ to Objects и LINQ to XML, позволяющая выполнять параллельные запросы. В большинстве случаев достаточно в начале запроса написать AsParallel() для того, чтобы все последующие операторы выполнялись параллельно. Внутренне использует TPL.
  • Coordination Data Structures (CDS) — набор структур, который используется для синхронизации и координации выполнения параллельных задач. Ее мы рассматривать в этой статье не будем.

Хочу предупредить, что эта библиотека не решает вопросов, связанных с потоковой безопасностью объектов. Если вы используете не ThreadSafe объекты, то сами должны следить за тем, чтобы объект использовался только одним потоком.

Что будем делать


В качестве примера предлагаю взять поиск простых чисел от 2 до заданного предела. Будем все числа из этого интервала проверять следующим статическим методом:

public class Prime
  {
    /// <summary>
    /// Determines whether the specified candidate is prime.
    /// </summary>
    /// <param name="candidate">The candidate.</param>
    /// <returns>
    ///   <c>true</c> if the specified candidate is prime; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsPrime(int candidate)
    {
      for(int d = 2; d <= candidate/2; d++)
      {
        if(candidate%d == 0)
          return false;
      }
      return candidate > 1;
    }
  }


* This source code was highlighted with Source Code Highlighter.

Для удобства создадим интерфейс:

  interface IPrimeFinder
  {
    /// <summary>
    /// Finds primes up to the specified limit.
    /// </summary>
    /// <param name="limit">The limit.</param>
    /// <returns>List of primes</returns>
    List<int> Find ( int limit );
  }


* This source code was highlighted with Source Code Highlighter.


Эталонный расчет


В качестве эталона будем считать простой цикл for от 2 до limit:

public List<int> Find(int limit)
    {
      var result = new List<int>();

      for(int i = 2; i < limit; i++)
      {
        if(Prime.IsPrime(i))
          result.Add(i);
      }

      return result;
    }


* This source code was highlighted with Source Code Highlighter.

Если мы запустим нашу программу, то увидим примерно следующий график загрузки процессора при поиске всех простых чисел до 200 000:

CPU load

Процессор работает только в пол силы. Попробуем это исправить.

Parallel.For


Большое преимущество этой библиотеки в том, что для создания многопоточного код а требуется минимум изменений. В нашем случае метод Parallel.For() принимает три параметра: начало интервала, конец и делегат для выполнения. Заметьте, что модификация не затрагивает тело цикла:

public List<int> Find(int limit)
    {
      var result = new List<int>();

      Parallel.For(2,    //start from
             limit, //up to
             i =>  //action variable
             {
               if(Prime.IsPrime(i))
                 result.Add(i);
             });

      return result;
    }


* This source code was highlighted with Source Code Highlighter.

Изменения минимальны, но давайте посмотрим, на график загрузки процессора:

Full cpu load

Используются оба ядра. И время выполнения сократилось почти в два раза (одна клетка — 5 секунд). Стоит заметить, что List<T> позволяет добавлять элементы в разных потоках. А вот если бы мы попытались пройтись по с списку с помощью foreach, то получили бы ошибку. Вся работа с синхронизацией так и лежит на пользователе.

AsParallel()


Одним из моих самых любимых новшеств в .net стал LINQ. Он позволяет сократить многие конструкции до одной строчки. Вот как будет выглядеть наш пример, записанный с помощью LINQ:

Enumerable.Range ( 2, limit - 1 ).Where ( Prime.IsPrime ).ToList ( );

* This source code was highlighted with Source Code Highlighter.

Коротко и ясно. Для того, чтобы сделать этот запрос параллельным нужно дописать всего одно слово AsParallel():

Enumerable.Range(2, limit - 1).AsParallel().Where(Prime.IsPrime).ToList();

* This source code was highlighted with Source Code Highlighter.

Все, что записано после AsParallel() будет выполнено в параллельных потоках, используя все процессоры. Как видно и в случае LINQ никаких изменений, затрагивающих содержание исходного кода, не потребовалось. Более того этот вариант более безопасен с точки зрения потоков: в данном случаем PLINQ сам синхронизирует выполнение. Можно так же использовать и агрегирующие функции.

Parallel.Invoke()


Parallel.Invoke() в основном используется для выполнения разноплановых задач. В нашем варианте мы разобьем все числа из диапазона на 10 частей и выполним расчет в виде параллельных задач. Для этого создадим дополнительный метод помощник, который в качестве аргументов получает верхнюю границу, количество поддиапазонов и номер поддиапазона для расчета:

private static List<int> CheckRange(int n, int limit, int factor)
    {
      var local_result = new List<int>();
      for(int i = n*(limit/factor); i < (n + 1)*(limit/factor); i++)
      {
        if(Prime.IsPrime(i))
          local_result.Add(i);
      }
      return local_result;
    }


* This source code was highlighted with Source Code Highlighter.

В качестве аргумента Parallel.Invoke() в нашем случае принимает массив делегатов. Новый код будет выглядеть так:

public List<int> Find(int limit)
    {
      var result = new List<int>();
      Parallel.Invoke(() => result.AddRange(CheckRange(0, limit, 10)),
              () => result.AddRange(CheckRange(1, limit, 10)),
              () => result.AddRange(CheckRange(2, limit, 10)),
              () => result.AddRange(CheckRange(3, limit, 10)),
              () => result.AddRange(CheckRange(4, limit, 10)),
              () => result.AddRange(CheckRange(5, limit, 10)),
              () => result.AddRange(CheckRange(6, limit, 10)),
              () => result.AddRange(CheckRange(7, limit, 10)),
              () => result.AddRange(CheckRange(8, limit, 10)),
              () => result.AddRange(CheckRange(9, limit, 10)));

      return result;
    }


* This source code was highlighted with Source Code Highlighter.

Мы, конечно, сильно изменили код, но это не типичная ситуация.

Немного статистики


Для наглядности привожу график скорости выполнения:

Execution time

И график загрузки процессора (немного растянул):

CPU load

На графике загрузки хорошо видны паттерны из трех методов, использующих параллельные вычисления и одного эталонного.

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

Дайте два!


  1. Страница загрузки Parallel Extensions
  2. Исходные коды примера: Look4Prime


И это все?!?


Нет, не все. Вот ссылки для более углубленного изучения:


_________
Текст подготовлен в ХабраРедакторе

комментарии (69)

  • Отлично! Просто супер! Спасибо!
  • «многие программы до сих пор умеют использовать только одним» — тут ошибка, или «использовать только один», или «пользоваться только одним».
    • Спасибо. Исправил.
  • не на atom 330 случайно гоняли?
  • Не знал про такое, большое спасибо!
    Полезная штука.
  • НЛО прилетело и опубликовало эту надпись здесь.
    • Скоро в мире будет один большой компьютер. И не надо будет домой покупать. Ведь если все компьютеры объединить, то мощности, которые сейчас простаивают с лихвой покроют все потребности на ближайшие несколько лет. =)
      • а потом злобные пираты отрежут океанический кабель и миллиарды человек останутся без работающих компьютеров :)
        централизация нужна в меру :)
        • Это наоборот распределение. Из одного супер-компьютера получится два полусупер-компьютера. И так можно резать пока опять у каждого не останется по одному. :)

          Я имел ввиду объединить имеющиеся.
          • а данные тоже хранить в полном объеме на каждом компьютере? ибо если разрезать то можно оттяпать кусок чего-нибудь интересного
            :)
    • а щас то че мешает?
      windows HPC server вышел. ставь и параллель, говорят его поднять легко)
      • НЛО прилетело и опубликовало эту надпись здесь.
    • Про такие вещи, как OpenMP и MPI программисты под .NET видимо не слышали :(
      • Ну OpenMP, вообще говоря, не панацея…
        Во-первых, насколько мне известно, он только для C++ и Fortran.
        Во-вторых, с помощью него не так просто распараллелить сложные алгоритмы. В частности, алгоритмы парадигмы «разделяй и властвую» (простой пример — быстрая сортировка), так просто не параллелятся…
        • Во-вторых, с помощью него не так просто распараллелить сложные алгоритмы.
          А с помощью Parallel Extensions, которые судя по описанию аналогичны OpenMP, это будет сделать легче? Нет. Что я для себя извлёк из этого: кому действительно нужно было увеличить быстродействие — написали нужный кусок кода на C+OpenMP, а не ждали пока сделают подобное для .NET. Думаю вы, как человек явно разбирающийся в теме, согласитесь, что слышать в нынешнее время фразу
          Так что скоро будем параллелить не только между процессорами, но и между компами. :)
          довольно странно.
          • Да, я с Вами полностью согласен. Мне вообще не нравится идея писать на .NET хоть сколь-нибудь серьезные вычисления. Зато GUI я пишу только на нем.

            Мой пост скорее о другом: что мир на OpenMP не сошелся и я, в частности, не сумел на нем решить свою задачу распараллеливания. Мне пришлось использовать относительно новую библиотеку Intel Threading Building Blocks.

            Ну а про распараллеливание между компами это да… Хотя знаете, иногда нужно быстро проверить идею. Ключевое слово «быстро». В этих случаях удобно это написать на .NET (например), а затем переносить на С++. Хотя, конечно, такой сценарий больше подходит для исследователей-одиночек, чем для компаний.
          • НЛО прилетело и опубликовало эту надпись здесь.
            • Хм, а мы с Kroz поняли друг друга :)
      • НЛО прилетело и опубликовало эту надпись здесь.
        • Наверное я невнимательно читал доступную по Dryad информацию. Если у вас найдётся минутка, может быть вы сможете аргументированно указать на явные прорывы? Буду только рад.

          P.S. OpenMP я противопоставляю новости в топике, о его особенностях я знаю. Хотя почему же, не так давно стало возможным применять его для автоматического распараллеливания и на системах с распределённой памятью.
          • НЛО прилетело и опубликовало эту надпись здесь.
            • Мне сложно примерять на себя роль промышленного программиста на .NET, но я понял вас. Да, для .NET, и в частности для веб приложений это является сейчас трудностью. Для большинства программистов под .NET эта технология действительно будет очень полезна. Жаль, что она появилась позже аналога для Fortran/C/C++. Кстати говоря, о MPI для C# я слышал ещё год назад, сейчас это считают готовым продуктом.
              Достоинства PE в том, что они позволяют в существующий проект внедрить параллельный код без особых проблем. Берётся, фактически, любое LINQ выражение в программе, и, при наличии нескольких ядер или процов, начинает обрабатываться параллельно. Без изучения тонкостей вопроса, без изучения нового языка программирования.
              Что ещё раз доказывает, что можно сравнивать PE и OpenMP :) В удобстве OpenMP для своих прямых и не только задач я убедился.
              Насколько я понимаю, Ваш вопрос таков: чем он лучше или круче MapReduce?
              Нет, его я хотел сравнить с MPI. Имхо они одного порядка решения, а MapReduce действительно упрощённый вариант.

              Просто жаль, что после того, как Microsoft не уделяла внимание HPC и более мирным параллельным технологиям, она начала выпускать продукты, как две капли воды воды похожие на давно известные решения :)
              И из этого вовсе не следует, что сейчас разработчик не пишет многопоточные программы, просто сейчас это требуется делать вручную и держать в голове очень много лишней информации. Если хотите, могу привести пару интересных примеров.
              Интересные примеры бесполезными никогда не будут. Не так часто удаётся прорваться и пообщаться с знающими людьми. Не сочтите снова за занудство, но только совсем простые примеры мне будут понятны, ибо мне приходится работать и на СКИФе, так что я немного в теме :)
              • НЛО прилетело и опубликовало эту надпись здесь.
                • LINQ полезная вещь, судя по тому что про неё попадается на глаза. Но если говорить об автоматическом распараллеливании, то Intel уже выпустила Cluster OpenMP, работающую на распределённых системах.

                  Не знаю. Там ведь фишка не только в архитектурном решении, там фишка в LINQ. Нормально параллелятся функциональные языки, и LINQ как раз такой подъязык. Подозреваю, что код на Си++ окажется просто сложнее, поскольку там вся функциональность в шаблонах.
                  Если параллелить по задачам, то думаю и нынешних средств хватит, всё-таки все примеры выше и в статье так или иначе относятся к распараллеливанию циклической обработки данных. Для этого в C(++) ничто не мешает особо :)

                  К сожалению мой интерес в области параллельного программирования не очень далеко распространяется, так что рассуждать о практической полезности создавать настолько автоматизированные средства мне сложно. Главное чтобы программисты не забывали о появляющихся при использовании этих средств последствиях, которые они не смогут скорее всего контролировать в таком случае.
                  • НЛО прилетело и опубликовало эту надпись здесь.
                    • :) Одно дело разная запись, другое — не согласуемые концепции языков. Как я уже сказал, второго я особо не вижу в параллелизации C++. Что касается [1..3..11], то это уже синтаксический сахар, который, в общем-то можно использовать и в императивных языках, если написать нужный препроцессор. А если использовать тот же OpenMP, то эту функцию можно записать и на C++ с возможностью распараллеливания.

                      Холивар я вижу лишь в том, что вы, как и многие приводящие этот и похожие примеры, сознательно сократили вычисления синуса в функциональном стиле, не учтя оптимизацию вычисления факториала в примере на C++ и сократив количество кода чисто языковыми конструкциями, которые можно ввести в любом языке. При идентичной сути эти фрагменты будут не так сильно отличаться, а распараллеливать можно и C++ код, для этого не нужно функциональное программирование :)
                      • НЛО прилетело и опубликовало эту надпись здесь.
                        • Я в конечном счёте считаю, что наличие для распараллеливания отдельной строчки #pragma omp parallel или .AsParallel() в LINQ не такая уж и сложность для пишущего программу :) Тем более, что компиляторы, поддерживающие OpenMP, обычно имеют опции для выявления конструкций, поддающихся распараллеливанию. Так что это тоже не теория, а вполне себе практика :)

                          Очень рад за .NET-чиков, что теперь и им это доступно. Надеюсь, что .AsParallel будет реализован во всех необходимых местах и костылей придётся писать меньше.
                          • Вот только непонятные «эти» программисты. Мы тут с вами довольно на интересную тему беседуем, а они уже забыли про новость :)
                            • Программисты занимаются внедрением. А на Си я к сожалению практически не писал и слова MapReduce и OpenMP мне мало о чем говорят. Но я с удовольствием читаю и делаю заметки по разбору на будущее.
                              Кстати, в PE есть недоработки. На то и CTP. PE в некоторых случаях (например у нас на дуал-ксеоне) при отсутствии работы начинает ее активно искать. При этом так активно, что занимает 80% от всех 8 ядер.
                              • Мне вчера попался интересный доклад на эту тему: channel9.msdn.com/pdc2008/TL25/. Аудитория там правда C++ программисты и для них Microsoft поддерживает OpenMP и MPI в оригинальном виде, однако в докладе много есть и про их специфичные инструменты, которые делаются сейчас для всех .NET языков. Если вас интересует, то в Википедии вполне достойно представлены эти технологии.
                                PE в некоторых случаях (например у нас на дуал-ксеоне) при отсутствии работы начинает ее активно искать. При этом так активно, что занимает 80% от всех 8 ядер.
                                Лишнее подтверждение тому, что от всех проблем эти абстракции не спасут. конечно же, они скорее всего исправят шедулер потоков, если дело в нём, но программист по хорошему тоже должен думать о том, чтобы все потоки выполнялись примерно одинаковое время. В конечном счёте это выгоднее :)
                                • Это известный им баг.

                                  Т.е. если просто запусть процесс веб-сервиса, отрезанный от мира, то он начинает в холостую гонять комп сразу после загрузки библиотеки. Хотя никаких операций с ней не производилось. На девелопмент-сервере такого эффекта нет.
                          • НЛО прилетело и опубликовало эту надпись здесь.
                            • Но ведь можно было написать и на функциональном языке этот пример с использованием цикла? А значит об использовании в данном месте map/reduce ему нужно отдельно думать, по крайней мере он должен был это где изучить/услышать/прочитать :) Особенно если его первым языком был какой-то из «последовательных». Так что в этом смысле нет разницы в том, «думает» ли программист при проектировании расчёта на языке map/reduce(а ведь не всё можно к нему свести), или думает по привычной ему схеме, пишет циклы и потом(либо по ходу действия) расставлять в коде комментарии OpenMP.

                              P.S. Посмотрю завтра, будет ли Intel Compiler предлагать распараллеливание цикла в вашем примере :)
                              • НЛО прилетело и опубликовало эту надпись здесь.
                                • Если человек научится, он сможет написать это и не на функциональном языке, это я и хотел сказать :) Благо средства есть.
                                  • НЛО прилетело и опубликовало эту надпись здесь.
                                    • Поживём — увидим :) Может быть даже жоведу себя до проведения тестов производительности, когда будет что сравнивать. Для меня это важнее, чем небольшое удобство при разработке :)
                                      • НЛО прилетело и опубликовало эту надпись здесь.
                                        • Я и не спорю, в этой области сейчас большой прогресс. Но всё равно останутся задачи, которые не решить простым распараллеливанием циклов :)

                                          Присоединяйтесь: Знакомы ли вы с параллельным программированием? :)
                                          • НЛО прилетело и опубликовало эту надпись здесь.
  • … вводным обзором в Parallel Extendions.
    подправьте «Extendions»… Правда полезная статья =)
    • Исправил. Спасибо :)
  • Полезно. Спасибо.
  • Огромное спасибо за столь полезную статью, как раз сейчас с этим сталкиваюсь, когда происходит длительная обработка данных, и при этом процессор загружен максимум на 30-40%, думаю теперь временные затраты на обработку уменьшатся вдвое, если не больше.
  • НЛО прилетело и опубликовало эту надпись здесь.
    • Лучше, наверное, чтобы каждое распараллеливалось: тогда при низком количестве обращений использование процессоров будет выше. Parralels должна сама следить за распределением нагрузки на процессоры.

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

      виртуализация — это тоже вариант
      • Говорят, в 2008-м сервере это уже не так актуально. Программисты Микрософта таки переписали sheduler потоков, так что теперь они привязываются к своим процессорам. А раньше потоки после простоя могли продолжить выполнение на другом ядре, в результате чего были накладки на переключение и перезапись кешей. Двигаются в правильном направлении :)
        • Вообще такой подход как вы описали — довольно таки сомнительный. Есть поток 1, он запустился на одном процессоре, запустился поток 2 и полностью загрузил процессор. Теперь если запрос к потоку 1 опять придет, то он будет вынужден выполняться медленно, так как уже привязан к этому процессору. А у вас там ещё 7 простаивают…

          Если брать на примере IIS6, то там потоки «старались» выполняться на тех же процессорах, но это было не гарантированно. Гарантированно запрос выполнялся именно на свободном процессоре.
          • Ко мне эта новость пришла из других рук. Но вы верно уточнили, «старались». Так вот если раньше этим нельзя было управлять, то в 2008-м такая возможность вроде как появилась. Просто если параллельно выполняются несколько вычислительных процессов на разных ядрах и известно, что нагрузку они создают одинаковую, то их лучше привязать намертво к процессорам. А раньше были частые случаи, когда при синхронизациях, и следовательно простоях, потоки перемещались между ядрами, что естественно приводило к падению производительности. А для серверов, работающих по потоковой схеме, это не должно быть так критично.
    • Мне кажется, лучше настроить на обработку первым свободным процессом, как в IIS 6. Так же если страница выполяет что-то тяжелое и сервер нагружен пользователями, то лучше эти операции делать при помощи асинхронных вычислений, чтобы не занимать поток на длительное время.

      PS. Сам в IIS7 этим не сталкивался… он позволяет распараллеливать каждое обращение на все процессоры?
      • Можно, например, увеличить у ApplicationPool'а количество Working Process. Тогда они будут занимать разные процессоры.
        • Конечно, это и получится то что я написал «настроить на обработку первым свободным процессом».

          Но так чтобы сам запрос параллелился — не думаю. Разве что использовать PE для расчетов, но это к IIS отношения не имеет.

          Я просто не знаю именно такой фичи как прозвучала в вопросе «чтобы каждое обращение распараллеливалось на все процессоры», чтобы это можно было организовать средствами IIS.
          • Понял. Такой я тоже не знаю. Видимо под распараллеливанием и понимались PE.
  • А что это за зубная паста на картинке :)?
    • По мотивам Aquafresh нарисовал. :)
      • Параллельная обработка зубов.
      • теперь даже при чистке зубов, буду вспоминать тему параллельности.
  • У вас неэффективный алгоритм проверки числа на простоту, т.к. нужно проверять до sqrt(b) а не до n/2. Боюсь что неэффективный алгоритм никакие Parallel Extensions не спасут =)
    • Данный алгоритм здесь исключительно для того, чтобы было чем занять процессор и не претендует на эффективность. Даже с квадратным корнем он никуда не годится :)
  • Надо учитывать что по лицензии (насколько я её понимаю) PFX нельзя использовать в готовых продуктах, только для тестирования.
    С другой стороны, Mono сделали свою версию — и в ней такого ограничения нет (но у меня не собралось).
  • Спасибо за статью!
    Кстати, цикл с простыми числами можно сократить до корня, и проверять хотя бы с шагом два (отбросив четные). А можно и еще быстреее :)
  • Меня терзают смутные сомнения. Вообще то задача управления потоков и разруливания принципов распределения ресурсов между процессорами — задача достаточно низкоуровнего характера. Т.е. по хорошему сама среда, в которой выполняется приложение пользователя должна заниматься этими делами, предоставляя возможность программисту бизнес приложений сконцентрироваться на бизнес-задачах соответственно. А здесь вместо этого мы имеем coupling с resource management и самому разработчику предлагается а) знать, на каких машинах будет выполняться приложение, б) вставлять в код приложения подобного рода затычки, если есть вероятность, что приложение установят на многопроцессорную машину. в) менеджить это все добро со всеми вытекающими последствиями.
    Вопрос — действительно ли стоит так восторгаться сим замечательным поделием? Может это просто hot-fix от микрософт перед выпуском фреймворка, который будет работать нормально на многопроцессорных системах?
    Да, впринципе может быть еще какой-нибудь подтип приложений, в которых нужно руками распределять нагрузку на процессоры, и там такой подход будет несомненно полезен. Но повальное большинство проектов к такому классу не относятся.
    • НЛО прилетело и опубликовало эту надпись здесь.
  • круть, наконец-то, Extensions — отличная штука :)
  • Насколько я знаю он войдёт в .Net 4.0, так что это действительно перспективная вещь.
  • Действительно крутая и удобная штука. Возьму на заметку, спасибо ;)
Только авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста.