1. C# / Говнокод #17489

    +95

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    double[,] ThreadMatrixMult(int size, double[,] m1, double[,] m2)
            {
                double[,] result = new double[size, size];
                var threads = new List<Thread>(size);
                for (int i = 0; i < size; i++)
                {
                    var t = new Thread( ti =>
                    {
                        int ii = (int)ti;
                        for (int j = 0; j < size; j++)
                        {
                            result[ii, j] = 0;
                            for (int k = 0; k < size; k++)
                            {
                                result[ii, j] += m1[ii, k] * m2[k, j];
                            }
                        }
                    });
                    threads.Add(t);
                    t.Start(i);
                }
                
                foreach (Thread thread in threads)
                    thread.Join();
    
                return result;
            }

    Запостил: LispGovno, 21 Января 2015

    Комментарии (49) RSS

    • "Самое главное в жизни - эффективность!" -- поговаривал мой начальник, выпив 3 кружки коньяка.
      Ответить
    • Хочу увидеть как оно будет множить матрицы 10 000x10 000.
      Хотя можно переписать на Эрланге, чё. Там создание и уничтожение потоков - дешевая операция.
      Ответить
      • Для умножения матриц давным-давно существуют выдроченные годами модули типа всяких BLAS'ов.
        Ответить
        • Спасибо, я знаю. Более того, такие здоровые матрицы обычно имеют не очень много ненулевых элементов и поэтому хранятся в разреженном виде. Очевидно, что умножать их можно куда быстрее.
          Ответить
      • >Там создание и уничтожение потоков - дешевая операция.
        Зато сложение и умножение - недешевая. Вроде тут на ГК писали что он в сотни раз дольше матрицы умножает, чем сишный код.
        Ответить
        • А сложение и умножение можно вынести в сишный модуль :)

          Ну серьезно, без смайликов и зеленого юмор уже непонятен?
          Ответить
          • просто на "Erlang" + "умножение матриц" сразу рефлекс сработал
            Ответить
    • >C#
      >t.Start(i);
      >thread.Join();
      Вот тебе бабушка и AssParallel.
      Не верю что в шарп до сих пор не завезли нормальные тредпулы c work stealing.
      Ответить
      • Task какие-то есть. Говорят лучше тредпулов. Я недавно постил их планировщик в другой теме про жэпэу
        Ответить
        • > Говорят лучше тредпулов.
          Они же исполняются на том самом тредпуле. Не?
          Ответить
          • вот так исполняется:
            http://govnokod.ru/17421#comment262107
            Ответить
            • Дык это деталь реализации тредпула, имхо. Сами по себе, без бекенда, который будет их исполнять, эти таски вообще ничего не умеют. Ну если я правильно понял идею.
              Ответить
              • Это отменябельный promise-контейнер результата. Бэкэнд естественно есть, эти таски пулы и возвращают.
                Ответить
              • Кстати, а вообще нафига завезли пакаджет_таск в кресты, когда есть стд фанкшен?
                Ответить
                • Чтобы самому не писать обёртки, которые создают промисы, берут фьючеры, вызывают функции, перехватывая исключения, и кладут результат в промис.
                  Ответить
                  • Кстати, а как они исключение умудрились сохранить и пробросить? Они же не клонируемы, емнип.
                    Ответить
                    • std::exception_ptr
                      std::make_exception
                      std::current_exception
                      О деталях я как-то не думал, но подозреваю, что там больше одного копирования (при выбрасывании) и не происходит, а исключения изначально живут в памяти, доступной всем потокам.
                      Ответить
                      • Std::exception_ptr типичный shared_ptr. Ну да. Для этого нужна поддержка компилятора на нижних уровнях.
                        Ответить
                      • О, прикольно. Сейчас поизучаем.

                        Меня вот это смущает: holding a reference to the exception object, or a copy of the exception object, it is implementation-defined if a copy is made. Передача потенциальной ссылки в соседний поток как-то подозрительно смотрится...
                        Ответить
                      • > исключения изначально живут в памяти, доступной всем потокам.
                        Похоже на то. Адреса не стековые, когда исключение ловишь. Походу throw их куда-то заныкивает, пока тип еще известен.
                        Ответить
                        • Да, потестил в gcc на своём исключении (не порожденном от std::exception). В момент выброса вызвался конструктор копирования, дальше вся работа шла уже с копией в куче. Довольно просто.

                          P.S. Т.е. throw само по себе может кинуть bad_alloc вместо нужного исключения?
                          Ответить
                          • > throw само по себе может кинуть bad_alloc вместо нужного исключения?

                            Может, конечно, если исключение аллоцирует много памяти.

                            Думаю, std::exception::what() возвращает const char* вместо std::string не просто так :)

                            Не исключено, что сами исключения могут аллоцироваться в отдельном резервном участке памяти (иначе как кинуть bad_alloc?), но это только догадки.
                            Ответить
                            • > иначе как кинуть bad_alloc
                              Заранее создать один экземпляр std::bad_alloс на чорный день. Его можно даже не пересоздавать, а всегда возвращать один и тот же. Он всё равно иммутабельный, и никто его не испортит.
                              Ответить
                            • > what() возвращает const char* вместо std::string не просто так
                              Вот это бесит, кстати. Из-за этого нельзя построить этот what() на лету.
                              Ответить
        • Плагиат помесь java.util.Future с монадическими методами (в 8ой тоже такое есть).
          Короче promise асинхронный стандартный. В js такого море.
          Ответить
          • Запутался в тредах. Не в том спрашиваю. А почему воркстилинг плох для ио? Помоему и его оптимизнет
            Ответить
            • Если идёт работа с длинными заданиями, или заданиями которые надолго блокируют поток, то профита от work-stealing особо нет.
              Говорят что он хорош когда много мелких заданий.

              http://literatejava.com/threading/fork-join-parallelization-feature-or-problem/
              http://stackoverflow.com/questions/8206318/is-javas-fork-and-join-thread-pool-is-good-for-executing-io-bound-task

              Проблема в оверхеде: work-stealing causes high contention
              Ответить
              • короче бы всех устроило, еслибы на время ожидания ио потоки бы возвращались бы обратно в пул и эффективность бы не терялась?
                Ответить
                • между прочем в сишарпе при юзанье async\await потоки реально возвращаются в тред пул и переиспользуются другими во время ио. ио ещё никогда не был так эффективен. а вы говорите асинкавайт не нужен. На его фоне стд асинк и фьючерсы сосут, ибо они такого не умеют. И крестовый асинк авайт этого тоже не будет уметь как я слышал и будет просто сахарком. вообщем крестовики как всегда соснули захлебываясь
                  Ответить
                • Не. Они там как-то возвращаются просто выигрыша нет. Плюс там оверхед в виде локальных очередей и скана по ним, вместо 1 очереди. Вот если у нас есть 1 касса, но покупатели настолько долго выбирают товар, и настолько быстро обслуживаются в кассе что они практически никогда друг с другом пересекаются какой смысл делать больше касс?

                  Очередь тут не выступает бутылочным горлышком.

                  Но допустим у нас 64 ядра. А задачи мелкие и быстро исполняются (микросекунды, сопоставимо со временем захвата лочки), то нам надо очень быстро накладывать задачи (желательно тоже в несколько потоков), и быстро их забирать. В случае рекурсивных заданий потоки сами же и ложат задания в очередь.

                  И тогда грубо говоря 32 потока ждут чтобы положить, и 32 ждут чтобы взять. И все они борются за право поменять одну и ту же область памяти (счётчик или указатель). И наступает пиздец...
                  В многопоточном кодинге важно чтобы не было бутылочных горлышек в виде мест, где расспаралелить нельзя (закон Амдала). Очередь это и есть такое место. Просто на типичных задачах при количествах ядер 4,8 это еще не было заметно.

                  Логичным и простым решением кажется сделать 64 очереди у 64 потоков.
                  А теперь советую подумать, как организовать точку входа, так, чтобы она раскладывала задания между очередями и не создавала contention?
                  Ответить
                  • > чтобы она раскладывала задания между очередями и не создавала contention?

                    раунд робин
                    Ответить
                    • > раунд робин
                      У лентяев много накопится, остальным делать нефиг будет.
                      Ответить
                    • Ок. (я так и думал) Не будем слушать борманда пока абстрагируемся от проблемы равномерной нагрузки.
                      Поэтому переходим к следующему логичному вопросу (который поцчему-то многие часто игнорируют)
                      Как сделать атомик счётчик чтоб 64 потока при его инкременте не соревновались за один и тот же участок памяти?
                      Ответить
                      • Хешем от адреса задачи мейби? Более-менее равномерно размажет (на системах с Гц).

                        Переместив контеншн в аллокатор, лол.
                        Ответить
                      • каждому по счетчику с рандомным значением начальной инициализации
                        Ответить
                  • > Они там как-то возвращаются
                    да никак они не возвращаются. тупо один поток из тредпула извлекли он реально занят на ожидании события. лажа вообщем тупое просирание процессорного времени, системных ресурсов и мегабайта под стек, что в высоконагруженных серверах жопа
                    Ответить
            • Как бы это объяснить.
              Проблема обычных пулов в том что они строятся на очереди, а очередь по сути двухпоточная (одна лочка или CAS на put, другая на take), и выше она не масштабируется.

              Вот надо придумать какую-то концепцию чтоб можно было добавлять задачи с N потоков и раскладывать их же по M исполнителям.
              То есть если брать очереди, то желательно иметь их M штук чтоб contentionа не было и оно параллелилось. Но как их координировать?

              Вот кто-то сел и подумал что есть рекурсивные задачи, которые внутри себя содержат другие задачи, итд, если их спаунить в локальной очереди, то contention сильно уменьшится. А координацию сделал через скан чужих очередей (пока чужой поток занят делом). Понятно что это порождает некий оверхед.

              В случае IO задачи просто не успевают исполняться так быстро, что потоки не успевают накладывать новые, то есть очередь НЕ становится бутылочным горлышком. Потому work-stealing не очень-то и нужен.

              Есть другой менее известный паттерн называется Disruptor. Там всё еще круче, правда IO он совсем не любит.

              Изложение немного сбойное и косноязычное, поскольку я устал, но надеюсь мысль зачем это всё городится понятна.
              Ответить
              • > но надеюсь мысль зачем это всё городится понятна.

                честно говоря - нет. по моему вы просто страдаете фигней.

                потоки и скедулинг были и будут оставатся относительно дорогими. потому что скедулинг был и все еще остается в сердце экспоненциальным алгоритмом - сколько бы на него O(1) помады не красили.

                если у вас lock contention на очереди задач становится проблемой, то это однозначная индикация что у вас задачи слишком мелкие.

                другими словами: если вы будете всякой мелкой херней спамить хардварные ресурсы (проц тоже железо!) то у вас никогда ничего скалироватся не будет.
                Ответить
    • Очень похоже на лабу типа "перемножьте матрицы с использованием многопоточности".
      Ответить
      • Новая игра ГК: отгадай условие лабы по коду.
        Ответить
    • Деревянная ключница "Покров Богородицы" Деревянная ключница "Покров Богородицы" настенная
      Цена: 9900 р.
      Размер: 29 х 34 х 9 см
      Материалы: Медь, Бархат, Дерево ® <a href=https://vipcu.ru/category/derevyannye-klyuchnitsy/>такой</a>

      <a href=https://vipcu.ru/derevyannaya-klyuchnitsa-tt-s-nagradami-sssr-16-276/><img>https://vipcu.ru/wa-data/public/shop/products/48/25/2548/images/4429/4429.200x0.jpg</img></a> <a href=https://vipcu.ru/derevyannaya-klyuchnitsa-nastennaya-denezhnoe-derevo/><img>https://vipcu.ru/wa-data/public/shop/products/58/08/858/images/4813/4813.200x0.jpg</img></a> <a href=https://vipcu.ru/derevyannaya-klyuchnitsa-podkova-s-zolocheniem-14-150/><img>https://vipcu.ru/wa-data/public/shop/products/43/25/2543/images/4404/4404.200x0.jpg</img></a>
      Ответить

    Добавить комментарий