2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




На страницу Пред.  1 ... 7, 8, 9, 10, 11  След.
 
 Re: Как писать быстрые программы
Сообщение11.11.2025, 13:00 
Yadryara в сообщении #1708864 писал(а):
Вы же не сказали чей он и не процитировали:

Ну я подумал, что раз я взял код с первой страницы эттй темы из вашего поста, то вы его вспомните :mrgreen:

 
 
 
 Re: Как писать быстрые программы
Сообщение11.11.2025, 14:49 
Аватара пользователя
mihaild в сообщении #1708866 писал(а):
Какого порядка числа?

1e40 — 1e46

 
 
 
 Re: Как писать быстрые программы
Сообщение12.11.2025, 21:17 
 i  Выделена тема «Как установить Ubuntu под Win10 в WSL»

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 15:15 
Аватара пользователя
Победил-таки вроде. Оказалось надо аllocatemem убрать из начала. Правда, файлы перестали создаваться рядом с прогой.

Код:
Время в секундах

PARI       Консоль
       gp.exe    wsl gp    Компиляция

66.6     66.1      69.2          34.2

На 93% быстрее.

wrest, а как использовать многопоточность? Мне же теперь надо программу запустить в 12 потоках.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 16:16 
Аватара пользователя
Dmitriy40
Вот уже и раньше замечал у Вас эту ошибку, но всё руки не доходили о ней сказать.

Dmitriy40 в сообщении #1709107 писал(а):
Yadryara в сообщении #1709104 писал(а):
На 93% быстрее.
На $1-34.2/66.1=48.3\%$. Если бы на 93% быстрее, то значит от 100% осталось всего 100%-93%=7% или 4.6с.

Нет. Это у Вас неверный расчёт соотношения скоростей.

Dmitriy40 в сообщении #1709107 писал(а):
Почти вдвое, прекрасно.

Именно что почти вдвое быстрее. То есть в 1.93 раза или на 93%.

Можно пример с бегнунами привести. Хорошие бегуны 500 метров могут пробежать за 66 секунд.

Когда речь идёт о том кто быстрее, то надо сначала среднюю скорость посчитать. А потом уже сравнивать.

В данном случае 720 паттернов считались за 66.1 секунды. Какая скорость была?
10.89 пат/сек.

А теперь те же самые 720 паттернов считаются за 34.2 секунды. Какая скорость стала? 21.05 пат/сек.

На сколько быстрее стала скорость счёта? $ \dfrac{21.05}{10.89} \approx 1.93$. На 93%.

Dmitriy40 в сообщении #1709116 писал(а):
Проценты всегда берутся от исходного числа, а не от конечного.

Ну так Вы сначала скорость вычислите, а потом уже проценты считайте. Да, от этой первоначальной скорости.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 16:23 
Yadryara в сообщении #1709106 писал(а):
а как использовать многопоточность? Мне же теперь надо программу запустить в 12 потоках.

Это так просто не работает :mrgreen:
Многопоточность штука непростая, и в pari/gp в том числе, надо читать доки по pari/gp. Я, честно сказать, не вникал там.
Например, надо чтобы циклы были длинные по времени, а вычисления внутри цикла не зависели друг от друга.
У вас же, насколько я понимаю (я ваш код не вникал), ситуация лучше подходит для запуска нескольких инстансов одного и того же кода, когда каждый считает свою "часть" независимо и не пользуясь результатами других инстансов из других частей. Памяти каждый инстанс потребляет у вас мало (128Мбайт на стек), так что несколько инстансов поместятся в памяти компа одновременно, и тогда уже операционная система будет делить время между ними сама.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 16:41 
Аватара пользователя
Yadryara в сообщении #1709120 писал(а):
Именно что почти вдвое быстрее. То есть в 1.93 раза или на 93%.


Это терепевтика.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 16:49 
Аватара пользователя
EUgeneUS, что за терепевтика ?

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 16:50 
Чтобы заставить pari считать многопоточно надо заменить внешний цикл for/forstep/forprime на parfor/parforstep/parforprime. Плюс все используемые глобальные переменные объявить в export(), а локальные для потока в my(). Плюс нельзя изменять глобальные переменные. Если всё же надо, то или print, или возвращать результат из внутренних циклов и во внешнем parXXX аккуратно менять глобальные переменные по полученному результату.
Пример (за несколько секунд вводит число 48789):
Код:
\\Было:
x=123; n=0;                         for(a=1,1e6, if(isprime(a^2+x), n++ );           ); print(n);
\\Станет:
x=123; n=0; export(x); my(nn=0); parfor(a=1,1e6, if(isprime(a^2+x), nn++); nn ,r,n+=r); print(n);
Здесь переменная r получает значение из локальной nn каждого потока после его окончания счёта (обратите внимание на отсутствие точки с запятой после nn!) и меняет глобальную переменную n. Никак иначе менять глобальные переменные в потоках нельзя.
Если надо вернуть больше одного числа, то на место nn ставим вектор/матрицу/список/map или вообще что угодно - и тогда r примет именно что поставили (и например станет вектором или матрицей) и можно будет с ним делать что хочется.

wrest
Да, потоки в этом смысле можно считать функциями (локальные переменные, возврат результата, отсутствие побочных эффектов) со специфическим синтаксисом.

-- 13.11.2025, 16:57 --

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

-- 13.11.2025, 17:01 --

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

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 17:04 
Аватара пользователя
wrest в сообщении #1709124 писал(а):
Памяти каждый инстанс потребляет у вас мало (128Мбайт на стек),

Так вот он не уложился:

Код:
12.   1 1   1 2   1 4     13 8   17 5   19 12
13.   1 1   1 2   1 5     13 8   17 5   19 14
3798208741512893953450806896467331273847893140        1 1 11  1 1 1   1 1     1        10

14.   1 1   1 2   1 6     13 8   17 5   19 16
2483278978764800109779492745452104360533523540        1 1 11111  1  111  1             12

15.   1 1   1 2   1 7     13 8   17 5   19 18
  ***   at top-level: init_Rab_41()
  ***                 ^-------------
  *** init_Rab_41: the PARI stack overflows !
  current stack size: 134217728 (128.000 Mbytes)
  [hint] set 'parisizemax' to a nonzero value in your GPRC

  ***   Break loop: type 'break' to go back to GP prompt
break> allocatemem(2^28)
  ***   Warning: new stack size = 268435456 (256.000 Mbytes).
?

Можно ли аккуратно продолжить с того же места?

-- 13.11.2025, 17:21 --

wrest в сообщении #1709135 писал(а):
Запустил 2 сессии (2 закладки в терминале) и одновременно в каждой скомпилированный код.
Оба инстанса gp завершились практически одновременно, за 50 сек.
Итого ускорение в (36:50)*4=~1,4 раза.

Это как? Или двоеточием не деление обозначено?

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 17:26 
Yadryara в сообщении #1709134 писал(а):
Можно ли аккуратно продолжить с того же места?

Я бы allocatemem вообще убрал бы из кода.
Отдайте ему всё, что можете, например
default(parisizemax,4*10^9)
и пусть сам разбирается. Причем, внести это раз и навсегда в файл инициализации (gprc).
Полный путь к файлу в линуксе /etc/gprc (имя файла gprc, без расширения).
Что вам и советует gp:
[hint] set 'parisizemax' to a nonzero value in your GPRC
Строчка в нём, у меня например (lf. так же предваряющий комментарий)
\\ Set maximal stack size. A safe value is half of the system memory.
parisizemax = 4G


Но дальше будет вопрос а сколько памяти отдаёт винда в wsl/mingw (в смысле не ограничивает ли).
Если вдруг ваш код захочет больше чем ему дают, то ОС его прибьёт, но судя по всему этого не случится при ваших 8ГБайт физической памяти (и 12Гбайт включая виртуальную). Но это уже в направлении «Как установить Ubuntu под Win10 в WSL»

Смотрите: то что можно ускорить алгоритмически, т.е. оптимизируя алгоритм и соответственно код - это сюда.
Здесь, грубо говоря, вам надо добиться (в смысле "как писать"), чтобы ваш код в интерпретаторе работал не 66, а скажем 6 секунд.
Т.е. был оптимизирован по вычислительной сложности.
А там -- "как запускать уже хороший код, чтобы он выполнялся быстро".

-- 13.11.2025, 17:28 --

Yadryara в сообщении #1709134 писал(а):
Это как? Или двоеточием не деление обозначено?

Деление. Это ошибка копипаста. Там же про два окна, так что и умножал на два.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 17:33 
wrest
Так строго разделить вряд ли удастся - оптимизация может (и будет!) зависеть от среды выполнения, то что быстрее на PARI/GP в режиме интерпретации может быть медленнее после компиляции, и наоборот. Например интерпретатор PARI/GP "любит" (в смысле оказывается быстрее) повторно вычислять несложные выражения типа $7x^3+19$ вместо того чтобы вычислить один раз и положить в переменную (как все нормальные языки), во всяком случае у меня.
Но если в принципе, то да, так поделить было бы лучше. А то начинается каша.
Я бы вообще смотрел на C код после компиляции и искал в нём причины замедления. Типа повторных вычислений, лишних вызовов функций, лишних присваиваний больших данных.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 17:46 
Аватара пользователя
Dmitriy40 в сообщении #1709141 писал(а):
Я бы вообще смотрел на C код после компиляции и искал в нём причины замедления. Типа повторных вычислений, лишних вызовов функций, лишних присваиваний больших данных.

С код я вижу, так что могу прислать. Сам-то ни бельмеса.

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 17:57 
Dmitriy40 в сообщении #1709141 писал(а):
Я бы вообще смотрел на C код после компиляции и искал в нём причины замедления. Типа повторных вычислений, лишних вызовов функций, лишних присваиваний больших данных.

Именно. Более того, я скормил код GP (до компиляции) в LLM (Qwen) и оно дало неплохие советы.

Ещё надо бы разобраться как писать так, чтобы заведомо небольшие числа транслировались бы в long.
Вот об этом:

(Оффтоп)

Declaring the types of variables allow gp2c to perform some optimisations. For example, the following piece of GP code

rho(n)=
{
local(x,y);

x=2; y=5;
while(gcd(y-x,n)==1,
x=(x^2+1)%n;
y=(y^2+1)%n; y=(y^2+1)%n
);
gcd(n,y-x)
}
generate the following output.
GEN rho(GEN n)
{
GEN x;
GEN y;
x = gdeux;
y = stoi(5);
while (gegalgs(ggcd(gsub(y, x), n), 1))
{
x = gmod(gaddgs(gsqr(x), 1), n);
y = gmod(gaddgs(gsqr(y), 1), n);
y = gmod(gaddgs(gsqr(y), 1), n);
}
return ggcd(n, gsub(y, x));
}
The function gsqr, gaddgs, gmod, ggcd, are generic PARI functions that handle gen objects. Since we only want to factor integers with this method, we can declare n, x y as int:

rho(n:int)=

{
local(x:int,y:int);
x=2; y=5;
while(gcd(y-x,n)==1,
x=(x^2+1)%n;
y=(y^2+1)%n; y=(y^2+1)%n
);
gcd(n,y-x)
}
The new C code output by gp2c is:
GEN rho(GEN n) /* int */
{
GEN x; /* int */
GEN y; /* int */
x = gdeux;
y = stoi(5);
while (cmpis(gcdii(subii(y, x), n), 1) == 0)
{
x = modii(addis(sqri(x), 1), n);
y = modii(addis(sqri(y), 1), n);
y = modii(addis(sqri(y), 1), n);
}
return gcdii(n, subii(y, x));
}
Now, the code now uses the more specific functions sqri, addis, modii, and gcdii.

The most efficient way to use typing is to declare some variables as small. This way this variable will be implemented by C long variables, which are faster than PARI integers and do not require garbage collecting. However, you will not be protected from integers overflow. For that reason, gp2c will automatically declare some loop indices as small when the range cannot cause overflow. However gp2c can be too conservative but you can force a loop index to be small with the syntax for(i:small=a,b,...).


-- 13.11.2025, 18:30 --

Dmitriy40
Например :mrgreen:
Берём такие функции и кладём в файл types.gp
Код:
f_small(st:small,en:small)=my(c:small);for(i:small=st,en,c++);return(c)
g_norm(a,b)=my(c=0);for(i=a,b,c++);return(c)

Запускаем в интерпретаторе:
Код:
? f_small(1,10^8)
cpu time = 9,140 ms, real time = 9,143 ms.
%1 = 100000000
? g_norm(1,10^8)
cpu time = 9,858 ms, real time = 9,858 ms.
%2 = 100000000

Одинаково. Ну ок.

Компилируем и запускаем:
Код:
? g_norm(1,10^8)
cpu time = 2,782 ms, real time = 2,786 ms.
%2 = 100000000
? f_small(1,10^8)
%3 = 100000000
? ##
  ***   last result: cpu time 0 ms, real time 0 ms.

Ноль! Ну я не знаю, может там компилятор шибко умный и просто подставляет вход на выход...

Код обоих в С (опция трансляции -g, т.е. со сборкой мусора со стека)

(Оффтоп)

Код:
/*-*- compile-command: "cc -c -o types.gp.o -g -O3 -Wall -fomit-frame-pointer -fno-strict-aliasing -fPIC -I\"/usr/include/x86_64-linux-gnu\" types.gp.c && cc -o types.gp.so -shared -g -O3 -Wall -fomit-frame-pointer -fno-strict-aliasing -fPIC -Wl,-shared -Wl,-z,relro types.gp.o -lc -lm -L/usr/lib/x86_64-linux-gnu -lpari"; -*-*/
#include <pari/pari.h>
/*
GP;install("init_types","v","init_types","./types.gp.so");
GP;install("f_small","lLL","f_small","./types.gp.so");
GP;install("g_norm","D0,G,D0,G,","g_norm","./types.gp.so");
*/
void init_types(void);
long f_small(long st, long en);
GEN g_norm(GEN a, GEN b);
/*End of prototype*/

void
init_types(void)          /* void */
{
  pari_sp ltop = avma;
  avma = ltop;
  return;
}

long
f_small(long st, long en)
{
  pari_sp ltop = avma;
  long c;
  {
    pari_sp btop = avma;
    long i;
    for (i = st; i <= en; ++i)
    {
      ++c;
      avma = btop;
    }
  }
  avma = ltop;
  return c;
}

GEN
g_norm(GEN a, GEN b)
{
  pari_sp ltop = avma;
  GEN c = gen_0;
  {
    pari_sp btop = avma;
    GEN i = gen_0;
    for (i = gcopy(a); gcmp(i, b) <= 0; i = gaddgs(i, 1))
    {
      c = gaddgs(c, 1);
      if (gc_needed(btop, 1))
        gerepileall(btop, 2, &c, &i);
    }
  }
  c = gerepilecopy(ltop, c);
  return c;
}


Хорошо видно, что c++ транслируется в С как
c = gaddgs(c, 1); в общем случае
++c в случае long
Ускорение примерно бесконечное :mrgreen:

 
 
 
 Re: Как писать быстрые программы
Сообщение13.11.2025, 19:07 
Аватара пользователя
wrest, играюсь с памятью. Хорошо, послушался вас: выделил почти 4 ГБ. 256 МБ ему тоже оказалось мало.

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

Впрочем могу ошибаться.

К советам ИИ в такой довольно сложной задаче пока отношусь скептически.

 
 
 [ Сообщений: 152 ]  На страницу Пред.  1 ... 7, 8, 9, 10, 11  След.


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group