Re: A16
От: nikholas Россия  
Дата: 10.02.09 09:06
Оценка: 13 (2)
Здравствуйте, vadimcher, Вы писали:

4-х головый Intel Xeon 1.6GHz, 64 бита, в среднем загрузка CPU 95%, занимает памяти 200 MB:


  0 days 00:00:00.000s:  1 :         10 (       586)
  0 days 00:00:00.000s:  2 :         53 (       586)
  0 days 00:00:00.000s:  3 :        242 (       586)
  0 days 00:00:00.000s:  4 :        377 (       586)
  0 days 00:00:00.015s:  5 :       1491 (       586)
  0 days 00:00:00.015s:  6 :       1492 (       586)
  0 days 00:00:00.015s:  7 :       6801 (      1456)
  0 days 00:00:00.015s:  8 :      14007 (      2266)
  0 days 00:00:00.140s:  9 :     100823 (     10009)
  0 days 00:00:03.468s: 10 :     559940 (     48503)
  0 days 00:00:13.875s: 11 :    1148303 (     94967)
  0 days 00:02:47.601s: 12 :    4036338 (    318504)
  0 days 00:02:47.601s: 13 :    4036339 (    318504)
  0 days 08:28:36.830s: 14 :   53619497 (   4135670)
  1 days 18:08:30.187s: 15 :  119476156 (   9202865)
  2 days 14:49:08.222s: 16 :  146226201 (  11260686)


Дальше считать не вижу смысла, 100 дней аптайма анрил...
Re[2]: A16
От: Seon  
Дата: 10.02.09 11:16
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Здравствуйте, vadimcher, Вы писали:


N>4-х головый Intel Xeon 1.6GHz, 64 бита, в среднем загрузка CPU 95%, занимает памяти 200 MB:



N>
N>  0 days 00:00:00.000s:  1 :         10 (       586)
N>  0 days 00:00:00.000s:  2 :         53 (       586)
N>  0 days 00:00:00.000s:  3 :        242 (       586)
N>  0 days 00:00:00.000s:  4 :        377 (       586)
N>  0 days 00:00:00.015s:  5 :       1491 (       586)
N>  0 days 00:00:00.015s:  6 :       1492 (       586)
N>  0 days 00:00:00.015s:  7 :       6801 (      1456)
N>  0 days 00:00:00.015s:  8 :      14007 (      2266)
N>  0 days 00:00:00.140s:  9 :     100823 (     10009)
N>  0 days 00:00:03.468s: 10 :     559940 (     48503)
N>  0 days 00:00:13.875s: 11 :    1148303 (     94967)
N>  0 days 00:02:47.601s: 12 :    4036338 (    318504)
N>  0 days 00:02:47.601s: 13 :    4036339 (    318504)
N>  0 days 08:28:36.830s: 14 :   53619497 (   4135670)
N>  1 days 18:08:30.187s: 15 :  119476156 (   9202865)
N>  2 days 14:49:08.222s: 16 :  146226201 (  11260686)
N>


N>Дальше считать не вижу смысла, 100 дней аптайма анрил...


Вот я не понимаю....
А что? очень тяжело сделать сохранение промежуточных результатов, с возможностью их загрузки и возобновления расчета...
И программу поставить в автозапуск в беловнормале? И хай себе крутится! куда нам спешить то?
Re: k нулей
От: Beam Россия  
Дата: 11.02.09 15:31
Оценка: 10 (1)
Intel Core 2 Duo 2.67 GHz. Загрузка процессоров 100%.
Работает достаточно быстро. Хотелось бы увидеть результат на 4-ядернике, но у меня такого нет.

2^100823 contains 9 zeros ===> 0 days 00:00:00.45
2^559940 contains 10 zeros ===> 0 days 00:00:06.75
2^1148303 contains 11 zeros ===> 0 days 00:00:23.38
2^4036338 contains 12 zeros ===> 0 days 00:03:58.80
2^4036339 contains 13 zeros ===> 0 days 00:03:58.81

1. Число состоит из "цифр" от 0000 до 9999 (unsigned short). Это позволило сделать умножение на степень двойки и определение количества нулей без использования деления — на таблицах. Таблицы готовятся заранее.
2. Используется обычное прыганье вперед на (нужное количество нулей — текущее количество нулей).
3. Алгоритм многопоточный, на основе OpenMP (надо включить поддержку в VS). Многопоточность используется при умножении и вычислении количества нулей. Причем потоки все вместе работают над одним и тем же большим числом (это удалось разрулить вроде).

Длинненько, но зато полный вариант:

#include <stdlib.h>
#include <time.h>
#include <memory.h>
#include <omp.h>

// #define TEST_MULT_COUNT

/////////////////////////////////////
/// BIG NUM

// число в памяти представлено в виде массива слов (unsigned short)
// каждое слово - число от 0000 до 9999
#define CELL_TYPE unsigned short
#define MAX_CELL 10000


/////////////////////////////////////
// УМНОЖЕНИЕ ЧИСЛА НА СТЕПЕНЬ ДВОЙКИ
/////////////////////////////////////

// mulTable - вспомогательная таблица для быстрого умножения чисел от 0000 до 9999 на степени двойки
// mulTable[x * 16 + pow] = struct {result, carry}
// x - число от 0000 до 9999
// pow - степень двойки, на которую умножается число x (от 0 до 15)
// result - результат умножения % 10000
// carry - перенос в соседнюю клетку = результат умножения / 10000 (т.е. то, что не поместилось в result)

#define MUL_TABLE_LEN 10000

struct MulInfo {
    CELL_TYPE result;
    CELL_TYPE carry;
};

MulInfo mulTable[MUL_TABLE_LEN*16];

void initMulTable() {
    for (int i = 0; i < MUL_TABLE_LEN; i++) {
        for (unsigned char pow = 0; pow < 16; pow++) {
            unsigned int y = i << pow;
            mulTable[(i << 4) + pow].result = y % 10000;
            mulTable[(i << 4) + pow].carry = y / 10000;
        }
    }
}

// умножение длинного числа arr длиной len на 2^pow
// результат записывается в arr2 (может совпадать с arr), новая длина записывается в len2 (может совпадать с len)
// многопоточная реализация - массив разбивается на равные части, каждый поток обрабатывает свою часть
void multArrayC(CELL_TYPE * arr, size_t len, CELL_TYPE * arr2, size_t & len2, int pow) 
{
#pragma omp parallel
    {
        CELL_TYPE carry = 0;
        unsigned int index = 0;    

        // поток берет каждый разряд и умножает его на степень двойки, прибавляя перенос если он есть
#pragma omp for
        for (int i = 0; static_cast<unsigned int>(i) < len; i++) {
            index = i;
            MulInfo x = mulTable[ (arr[i] << 4) + pow ];
            CELL_TYPE res = x.result + carry;
            carry = res >= 10000 ? (res -= 10000, x.carry + 1) : x.carry;
            arr2[i] = res;
        }
        // после обработки своей части массива у нас остался перенос carry, который относится к "чужой части"
        // в index хранится индекс последнего обработанного потоком "разряда"

        // каждый поток прибавляет оставшийся в конце перенос к следующему "разряду"
        // один из потоков, который обрабатывал самые старшие "разряды" числа, при необходимости, увеличивает длину массива
        index++;    // index = i + 1
#pragma omp critical
        if (carry != 0) {
            if (index < len) {
                arr2[ index ] += carry;
                if (arr2[index] >= 10000) {
                    arr2[index] -= 10000;
                    arr2[index+1]++;
                }
            } else {
                len2++;
                arr2[index] = carry;
            }
        }
    }
}


/////////////////////////////////////
// ПОДСЧЕТ КОЛИЧЕСТВА НУЛЕЙ
/////////////////////////////////////

// zerosTable - вспомогательная таблица для быстрого поиска нулей в числах от 0000 до 9999
// учитываются только нули в начале и в конце таких чисел, нули в середине не подсчитываются
// zerosTable[x] = struct {left, right}
// x - число от 0000 до 9999
// left - количество начальных (левых) нулей
// right - количество конечных (правых) нулей

#define ZEROS_TABLE_LEN 10000

struct ZerosInfo {
    unsigned char left;
    unsigned char right;
};

ZerosInfo zerosTable[ZEROS_TABLE_LEN];

void initZerosTable() {
    zerosTable[0].left = 0;
    zerosTable[0].right = 4;

    for (int i = 1; i < ZEROS_TABLE_LEN; i++) {
        zerosTable[i].left = 0;
        zerosTable[i].right = 0;

        char buf[10];
        sprintf_s<10>(buf, "%04d", i);

        int len = 4;
        for (int x = len-1; x >= 0 && buf[x] == '0'; x--) 
            zerosTable[i].right++;
        for (int x = 0; x < len && buf[x] == '0'; x++) 
            zerosTable[i].left++;
    }
}

// возвращает максимальное количество последовательных нулей в числе arr длиной len
// многопоточная реализация - массив разбивается на части, каждый поток обрабатывает свою часть
int countZeros(CELL_TYPE * arr, size_t len) {
    char globalMax = 0;
#pragma omp parallel
    {
        char max = 0;
        char cur = 0;
        CELL_TYPE x;
        int index = -1;
        // каждый поток подсчитывает нули в своей части
#pragma omp for
        for (int i = 0; static_cast<unsigned int>(i) < len-1; i++) {
            index = i;
            x = arr[i];
            cur += zerosTable[x].right;
            if (x != 0 && max >= cur) {
                cur = zerosTable[x].left;
            } else if (x != 0) {
                max = cur;
                cur = zerosTable[x].left;
            }
        }
        // после обработки каждый поток хранит в max максимальное количество нулей в его части
        // в index хранится индекс последнего обработанного потоком "разряда" или -1 если цикл не выполнялся

        // если поток что-то обрабатывал, обработаем еще один дополнительный (старший) "разряд"
        if (index != -1) {
            cur += zerosTable[ arr[index+1] ].right;
            max = max < cur ? cur : max;

            // globalMax - максимальное количество нулей в массиве
#pragma omp critical
            globalMax = max > globalMax ? max : globalMax; 
        }
    }
    return globalMax;
}

////////////////////////////////////////////
/// LOGGING

// печать длинного числа в файл и консоль
void print_num(FILE * f, CELL_TYPE * arr, size_t len) {
    fprintf(f, "%d ", (int)arr[len-1]);
    for (int i = len-2; i >= 0; i--) {
        fprintf(f, "%04d ", (int)arr[i]);
    }
    fflush(f);
}

void printProgress(FILE * f, int i, clock_t start, clock_t end) {
    double duration = (end - start) * 1.0f / CLOCKS_PER_SEC + 0.00000005;
    int t = (int) duration; // seconds
    double s = t % 60 + (duration - t);
    int m = t / 60 % 60;
    int h = t / 60 / 60 % 24;
    int d = t / 60 / 60 / 24;

    fprintf(f, "%12d mln ===> %d days %02d:%02d:%05.2f ===> %5.2f * 10^9 bits per sec\n", 
        i, d, h, m, s, i * 1.0f * (i+1) / 2e9 / duration);
    fflush(f);
}

void printResult(FILE * f, int i, int k) {
    fprintf(f, "\n!!!!! 2^%d contains %d zeros\n\n", i, k);
    fflush(f);
}

////////////////
///// MAIN 
////////////////

CELL_TYPE * arr;

int _tmain(int argc, _TCHAR* argv[]){
    // init tables
    initZerosTable();
    initMulTable();

    // file for logging
    FILE * f = fopen("log.txt", "w");

    // arr = 2^0
    arr = (CELL_TYPE *) malloc(1000*1000*1000);    // хватит на 6.500.000.000 степеней двойки
    size_t len = 1;
    arr[0] = 1;

    // time measure
    clock_t start = clock();

    int i = 0;        // текущая степень двойки
    int last = 0;    // последняя степень двойки, информацию о которой записывали в файл
    int k = 2;        // ищем степень двойки с таким количеством нулей

#ifdef TEST_MULT_COUNT
    for (int i=1; i < 100; i++) {
        //doubleArrayC(arr, len);
        multArrayC(arr, len, arr, len, 1);
        printf("i = %d ===> ", i);
        print_num(arr, len);
        printf(" ===> zeros = %d", countZeros(arr, len));
        printf("\n");
    }
#else
    while (true) {

        // каждую тысячу выводим сообщение, чтобы показать что не зависли
        if (i - last > 100000) {
            last = i;
            printProgress(f, i, start, clock());
            printProgress(stdout, i, start, clock());
        }

        int zeros = countZeros(arr,len);

        // если нашли нужное количество нулей, то выводим результат
        while (zeros >= k) {
            printProgress(f, i, start, clock());
            printProgress(stdout, i, start, clock());
            printResult(f, i, k);
            printResult(stdout, i, k);
            k++;
        }

        // "прыгаем" на расстояние (нужноеКоличествоНулей - найденноеКоличествоНулей)
        // нельзя прыгнуть дальше 13, иначе переписывать функцию multArray 
        int step = k - zeros <= 13 ? k - zeros : 13;
        multArrayC(arr, len, arr, len, step);
        i += step;
    }
#endif

    free(arr);
    puts("Press Enter\n");
    getchar();

    return 0;
}


И еще.

Можно было бы ускорить все это, используя SSE инструкции. Но мне пока непонятно как на основе арифметических функций и минимума if-ов реализовать умножение на степень двойки. Возьмем например, 128-битное число. В него влезет 8 "разрядов". ABCDEFGH. При умножении на 2^x должно получиться A'B'C'D'E'F'G'H' и некий перенос в следующий разряд. Вот как такое реализовать оперируя этим 128-битным числом как единым целым? Ну или 64-битным.

Еще я экспериментировал с алгоритмом nikholas (с "хитрым" прыганьем). И сделал вывод, что "простое прыганье" работает не медленнее, чем "хитрое". Причина в том, что умножение на большую степень двойки производится "столбиком". Т.е. если надо умножить некое большое число на 2^x и это число включает в себя n "разрядов", то мы должны сделать n больших умножений и потом n больших сложений. При умножении же на маленькую степень мы должны сделать одно большое умножение и одно большое сложение (переносы).
Вот и получается, что время умножения на большую степень двойки сравнимо с последовательным умножением несколько раз на маленькую степень двойки (если алгоритм учитывает, что умножение произодится только на маленькую степень). Например, время умножения на 2^22, сравнимо с умножением на 2^13 и затем на 2^9.
Best regards, Буравчик
Re[2]: k нулей
От: vadimcher  
Дата: 11.02.09 17:26
Оценка: +1
Здравствуйте, Beam, Вы писали:

B>Intel Core 2 Duo 2.67 GHz. Загрузка процессоров 100%.

B>Работает достаточно быстро. Хотелось бы увидеть результат на 4-ядернике, но у меня такого нет.

B>2^100823 contains 9 zeros ===> 0 days 00:00:00.45

B>2^559940 contains 10 zeros ===> 0 days 00:00:06.75
B>2^1148303 contains 11 zeros ===> 0 days 00:00:23.38
B>2^4036338 contains 12 zeros ===> 0 days 00:03:58.80
B>2^4036339 contains 13 zeros ===> 0 days 00:03:58.81

B>1. Число состоит из "цифр" от 0000 до 9999 (unsigned short). Это позволило сделать умножение на степень двойки и определение количества нулей без использования деления — на таблицах. Таблицы готовятся заранее.

B>2. Используется обычное прыганье вперед на (нужное количество нулей — текущее количество нулей).
B>3. Алгоритм многопоточный, на основе OpenMP (надо включить поддержку в VS). Многопоточность используется при умножении и вычислении количества нулей. Причем потоки все вместе работают над одним и тем же большим числом (это удалось разрулить вроде).

Вопрос: а зачем ты так делаешь
        int step = k - zeros <= 13 ? k - zeros : 13;
        multArrayC(arr, len, arr, len, step);

когда вроде можно так
        int step = k - zeros;
        while (step > 13) {
            multArrayC(arr, len, arr, len, 13);
            step -= 13;
        }
        multArrayC(arr, len, arr, len, step);

А вот зайца кому, зайца-выбегайца?!
Re[3]: k нулей
От: Beam Россия  
Дата: 11.02.09 22:24
Оценка:
Здравствуйте, vadimcher, Вы писали:

V>Вопрос: а зачем ты так делаешь

V>
V>        int step = k - zeros <= 13 ? k - zeros : 13;
V>        multArrayC(arr, len, arr, len, step);
V>

V>когда вроде можно так
V>
V>        int step = k - zeros;
V>        while (step > 13) {
V>            multArrayC(arr, len, arr, len, 13);
V>            step -= 13;
V>        }
V>        multArrayC(arr, len, arr, len, step);
V>


Да, можно. Вернее даже нужно (теоретически). Поправлю.
Но, к сожалению, это мало повлияет на результат
Если интересно, вот некоторая укрупненная статистика.

При расчете до степени 4 млн. (т.е. искали до 12 нулей) с помощью прыжков мы прыгали в 95% случаев на шаг от 5 до 7 (на 6 — 44%, на 7 — 35%, на 5 — 17%).
При увеличении искомого количества нулей эти цифры смещаются в большую сторону, но очень медленно, и прыжок на 14 (и более) понадобится нам при поиске a(19), a(20)

Если мы рассмотрим все степени двойки от 0 до 4 млн. (подряд, без прыжков), то процентное отношение количества нулей в них будет такое:
4 нулей — 8%, 5 нулей — 51%, 6 нулей — 33%, 7 нулей — 4%. Т.е. 84% степеней содержат 5-6 нулей, а 95% содержат 4-7 нулей.


Насчет расчета a(18).
Если посмотреть последовательность a(), то можно увидеть, что следующий член может увеличиваться до 10 раз!
Предположим, что так оно и есть, т.е. a(18) = 10^10. В алгоритмах используется приблизительное соотношение 1 байт = 2 десятичные цифры.
Значит нам понадобится 10^10 / 2 = 5 * 10^9 байт памяти для работы с числом.
Далее. Время. У меня в программе вычисляется средняя скорость работы программы. В битах в секунду. Где биты — количество бит в обработанных степенях двойки.
т.е. для 2^n обрабатывается n бит. А для всех чисел от 2^0 до 2^n соответственно n(n+1)/2 бит. Так вот средняя скорость на двухядерниках пока не превышала 50*10^9 бит/сек (обычно 30 млрд. бит / сек). Т.е. для 10^10 * 10^10 / 2 = 10^20 / 2 / 50 / 10^9 = 10^(20 — 2 — 9) = 10^9 секунд. 30 лет.

Хотя конечно для a(18) = 1.5 млрд все не так плохо. 1 Гб памяти + пол-года. Так что попробовать можно
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[2]: k нулей
От: Beam Россия  
Дата: 11.02.09 22:41
Оценка:
B>Вот и получается, что время умножения на большую степень двойки сравнимо с последовательным умножением несколько раз на маленькую степень двойки (если алгоритм учитывает, что умножение произодится только на маленькую степень). Например, время умножения на 2^22, сравнимо с умножением на 2^13 и затем на 2^9.

Кстати, если я не ошибся и это действительно так, то не все способы распараллеливания между несколькими компьютерами могут работать.
Например, не будет работать метод, в котором один компьютер подсчитывает степени от 0 до 1 млн., второй от 1 до 2 млн. и т.д., т.к. чтобы сообщить второму компьютеру число 2^(1 млн.) надо его сначала посчитать, а значит сдеолать большую часть работы.
Можно, конечно, передавать не само число, а просто степень (например 1 млн), но тогда тот компьютер должен либо опять же посчитать степени от 0 до 1 млн, либо перевести число 2^n в десятичную форму. Насколько мне известно, быстрых алгоритмов для этого не существует. Их сложность O(n^2), т.е. фактически такая же, как и при ручном расчете.

Я ничего не напутал?
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[2]: k нулей
От: nikholas Россия  
Дата: 12.02.09 11:10
Оценка: 5 (1)
Здравствуйте, Beam, Вы писали:

По поводу подсчета нулей:

Лучше сделать так: по числу x от 0000 до 9999 по таблице получаем zero-mask: на месте нулей — нули, на месте не-нулей — единицы (получаем число от 0 до 15)
для текущей позиции при подсчете нулей помним: максимальное число нулей найденое ранее (max, 0 — 19), число нулей на конце(curr, 0-19)
и далее по таблице [max, curr, zero-mask] получаем новые значения для max и curr
Для того чтоб не заморачиваться насчет конца числа curr может быть больше max, тогда в конце, после любого кол-ва финишных нулей, мы получаем точное значение max(главное в таблице сделать curr(19)->curr(19), а не то...).

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

B>Еще я экспериментировал с алгоритмом nikholas (с "хитрым" прыганьем). И сделал вывод, что "простое прыганье" работает не медленнее, чем "хитрое". Причина в том, что умножение на большую степень двойки производится "столбиком". Т.е. если надо умножить некое большое число на 2^x и это число включает в себя n "разрядов", то мы должны сделать n больших умножений и потом n больших сложений. При умножении же на маленькую степень мы должны сделать одно большое умножение и одно большое сложение (переносы).


Я особо не мерял но у меня сложилось впечатление что умножение на 3-х разрядное число (с подсчетом нулей) практически не отличается по времени от умножения на одноразрядное.

И возведение в большую степень работает быстро — я прикидывал, что 2^(10^9) можно получить дней за 7-8
Re[2]: k нулей
От: Seon  
Дата: 12.02.09 12:33
Оценка:
Здравствуйте, Beam, Вы писали:
B>
B>            mulTable[(i << 4) + pow].result = y % 10000;
B>            mulTable[(i << 4) + pow].carry = y / 10000;
B>


Здесь можно существенно убыстрить, если заменить деления чем нибудь более быстрым. условиями например...
Re[3]: k нулей
От: nikholas Россия  
Дата: 12.02.09 12:47
Оценка:
Здравствуйте, Seon, Вы писали:

S>Здравствуйте, Beam, Вы писали:

B>>
B>>            mulTable[(i << 4) + pow].result = y % 10000;
B>>            mulTable[(i << 4) + pow].carry = y / 10000;
B>>


S>Здесь можно существенно убыстрить, если заменить деления чем нибудь более быстрым. условиями например...


Это всего лишь инициализация таблицы

кстати, по моим ощущениям табличное "умножение" проигрывает в скорости двум делениям непосредственно в цикле(/ %)
а вот а if-ов надо стараться избегать...
Re[4]: k нулей
От: Beam Россия  
Дата: 12.02.09 13:38
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Это всего лишь инициализация таблицы

Да, именно так.

N>кстати, по моим ощущениям табличное "умножение" проигрывает в скорости двум делениям непосредственно в цикле(/ %)

Я тоже так думал, но оказалась, что таблицы выигрывают около 20%. Даже по отношению к оптимизированному компилятором коду: он достаточно умен, чтобы определить, что нужен и результат и остаток, поэтому не делает два деления. Во-вторых он это деление заменяет на умножение. Но все равно таблицы быстрее. Хотя может все зависит от кэша и т.п.

N>а вот а if-ов надо стараться избегать...

Это точно.
Best regards, Буравчик
Re[3]: k нулей
От: Beam Россия  
Дата: 12.02.09 14:14
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Лучше сделать так: по числу x от 0000 до 9999 по таблице получаем zero-mask: на месте нулей — нули, на месте не-нулей — единицы (получаем число от 0 до 15)

N>для текущей позиции при подсчете нулей помним: максимальное число нулей найденое ранее (max, 0 — 19), число нулей на конце(curr, 0-19)
N>и далее по таблице [max, curr, zero-mask] получаем новые значения для max и curr
N>Для того чтоб не заморачиваться насчет конца числа curr может быть больше max, тогда в конце, после любого кол-ва финишных нулей, мы получаем точное значение max(главное в таблице сделать curr(19)->curr(19), а не то...).

Отличная идея!
Мне приходила в голову похожая — я заводил таблицу [0000..9999, cur, max] = {new_cur, new_max}, но это работало долго, из-за большого размера таблицы (20*20*10000 = 4000000 = 4 Мб).
Здесь же получается таблица [0000..9999] = {mask} (10 кБ) и [max, cur, mask] = {new_cur, new_max} (20*20*16*2 = 12,8 кБ). Поэтому должно быть быстрее => внесу изменения.

N>Также лучше совместить подсчет нулей с возведением в степень — кеши и все такое


Это да. Пока что не сделал, т.к. возможна ситуация, что умножать будет нужно, а нули считать нет. Но если такое не понадобится, то объединю.

N>Я особо не мерял но у меня сложилось впечатление что умножение на 3-х разрядное число (с подсчетом нулей) практически не отличается по времени от умножения на одноразрядное.

N>И возведение в большую степень работает быстро — я прикидывал, что 2^(10^9) можно получить дней за 7-8

Я мерял, но может что-то упустил из виду. Было бы интересно увидеть твои замеры умножения. Ну хотя бы 1 млн, 5 млн, 10 млн (бех подсчета нулей). Все-таки если умножение идет "столбиком", то я реально не понимаю почему может быть быстрее
Best regards, Буравчик
Re[3]: k нулей
От: vadimcher  
Дата: 12.02.09 17:13
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Также лучше совместить подсчет нулей с возведением в степень — кеши и все такое


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

А вот зайца кому, зайца-выбегайца?!
Re[2]: k нулей
От: vadimcher  
Дата: 12.02.09 22:17
Оценка: 10 (1)
Здравствуйте, Beam, Вы писали:

B>Intel Core 2 Duo 2.67 GHz. Загрузка процессоров 100%.

B>Работает достаточно быстро. Хотелось бы увидеть результат на 4-ядернике, но у меня такого нет.

B>2^100823 contains 9 zeros ===> 0 days 00:00:00.45

B>2^559940 contains 10 zeros ===> 0 days 00:00:06.75
B>2^1148303 contains 11 zeros ===> 0 days 00:00:23.38
B>2^4036338 contains 12 zeros ===> 0 days 00:03:58.80
B>2^4036339 contains 13 zeros ===> 0 days 00:03:58.81

Я еще раз просмотрел твой код на предмет поиска нулей, и пришел к выводу, что возможны ошибки при k=2 и k>=5. Стал сравнивать результаты. Выяснилось: ошибки при k=2,3.

Для k=2, понятно. Два нуля оказались внутри 4-хзначной цифры. (Вероятность этого была достаточно маленькая, но я решил проверить, вдруг ты попал как раз в эту вероятность? Попал!) Так что и по логике, и на практике следует начинать с k=3.
Для k=3 у меня выдал 243 вместо 242. Почему? Непонятно. Я этого не ожидал.
Для k>=5. Когда один процессор обработал блок, он смотрит, какое там число стоит дальше, cur += zerosTable[ arr[index+1] ].right. Но что, если там стоит 0000, тогда надо двигать дальше, пока не встретится наконец ненулевое число! Т.е. надо что-то типа int j = 1, z; do { z = zerosTable[ arr[index+j] ].right; cur += z; } while (z == 4);
Тут проблема не только в том, что можно пропустить искомое число нулей, но и в том, что иногда шаг, с которым ты прыгаешь к следующему числу может оказаться больше, чем положено.
Верно?

А вот зайца кому, зайца-выбегайца?!
Re[3]: k нулей
От: Beam Россия  
Дата: 12.02.09 23:14
Оценка:
Здравствуйте, vadimcher, Вы писали:

V>Я еще раз просмотрел твой код на предмет поиска нулей, и пришел к выводу, что возможны ошибки при k=2 и k>=5. Стал сравнивать результаты. Выяснилось: ошибки при k=2,3.

V>Для k=2, понятно. Два нуля оказались внутри 4-хзначной цифры. (Вероятность этого была достаточно маленькая, но я решил проверить, вдруг ты попал как раз в эту вероятность? Попал!) Так что и по логике, и на практике следует начинать с k=3.
Да верно. Я просто об этом не упомянул. Для 1 и 2 это может не работать.
И это было бы ерундой, если бы не ...

V>Для k=3 у меня выдал 243 вместо 242. Почему? Непонятно. Я этого не ожидал.

Я тоже не ожидал Это реальный баг.
При подсчете он попадает на 240. Считает нули. На самом деле их там максимум 2, но также есть и по одному.
Но! Они все попадают в середину четырехзначных цифр А значит не учитываются. В итого думаем что количество нулей = 0, прыгаем на 3-0, т.е. на 3 => 240 + 3 = 243

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

V>Для k>=5. Когда один процессор обработал блок, он смотрит, какое там число стоит дальше, cur += zerosTable[ arr[index+1] ].right. Но что, если там стоит 0000, тогда надо двигать дальше, пока не встретится наконец ненулевое число! Т.е. надо что-то типа int j = 1, z; do { z = zerosTable[ arr[index+j] ].right; cur += z; } while (z == 4);

Все таки не до конца я разрулил потоки

V>Тут проблема не только в том, что можно пропустить искомое число нулей, но и в том, что иногда шаг, с которым ты прыгаешь к следующему числу может оказаться больше, чем положено.

Да. Маловероятно, но верно => исправлю. Спасибо
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[2]: A16
От: Beam Россия  
Дата: 14.02.09 21:48
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Здравствуйте, vadimcher, Вы писали:


N>4-х головый Intel Xeon 1.6GHz, 64 бита, в среднем загрузка CPU 95%, занимает памяти 200 MB:


N>
N>  0 days 00:00:00.000s:  1 :         10 (       586)
N>  0 days 00:00:00.000s:  2 :         53 (       586)
N>  0 days 00:00:00.000s:  3 :        242 (       586)
N>  0 days 00:00:00.000s:  4 :        377 (       586)
N>  0 days 00:00:00.015s:  5 :       1491 (       586)
N>  0 days 00:00:00.015s:  6 :       1492 (       586)
N>  0 days 00:00:00.015s:  7 :       6801 (      1456)
N>  0 days 00:00:00.015s:  8 :      14007 (      2266)
N>  0 days 00:00:00.140s:  9 :     100823 (     10009)
N>  0 days 00:00:03.468s: 10 :     559940 (     48503)
N>  0 days 00:00:13.875s: 11 :    1148303 (     94967)
N>  0 days 00:02:47.601s: 12 :    4036338 (    318504)
N>  0 days 00:02:47.601s: 13 :    4036339 (    318504)
N>  0 days 08:28:36.830s: 14 :   53619497 (   4135670)
N>  1 days 18:08:30.187s: 15 :  119476156 (   9202865)
N>  2 days 14:49:08.222s: 16 :  146226201 (  11260686)
N>


У меня тоже посчиталось a(16).

a(14) = 53'619'497 ===> 10 час 16 мин (21% медленнее)
a(15) = 119'476'156 ===> 2 дня 00 час 36 мин (15% медленнее)
a(16) = 146'226'201 ===> 2 дня 22 час 38 мин (12% медленнее)

В принципе, результаты по времени сравнимы с предыдущим полученным a(16). Хотя и получилось немного медленнее, необходимо учитывать разницу в количестве ядер и частоте.
Т.к. архитектуры у процессоров вроде одинаковые (Core), то можно сравнивать по частоте:
4-х головый Intel Xeon 1.6GHz ~~ 1.6 * 4 = 6.4 ГГц
2-я ядерный Intel Core 2 Duo 2.67 GHz ~~ 2.67 * 2 = 5.3 ГГц
Т.е. программа на 2-ядернике должна быть на 17% медленнее. Как видно, ситуация даже лучше (15%, 12%), т.е. имеется незначительное улучшение. Но это в теории, как будет в реальности на 4-ядернике — неизвестно

N>Дальше считать не вижу смысла, 100 дней аптайма анрил...


Дальше считать действительно нет смысла. Если уж и счтать то начиная с 1 млрд., чтобы искать a(18), а так... какой смысл.
Но a(18) один компьютер будет рассчитывать от 0 до 30 лет. Чтобы максимум уменьшить хотя бы до 1 года, надо 30 компьютеров. Сомневаюсь, что на RSDN найдется столько желающих.

Тем более, мне так и не понятна эффективная схема распределения работы между компьютерами по сети.

P.S. В общем, пока я для себя эту тему закрываю. Пока не придумается какая-нибудь эвристика (из области математики) для вычислений... Интересная задачка, однако.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[3]: A16
От: vadimcher  
Дата: 15.02.09 04:36
Оценка:
Здравствуйте, Beam, Вы писали:

B>P.S. В общем, пока я для себя эту тему закрываю. Пока не придумается какая-нибудь эвристика (из области математики) для вычислений... Интересная задачка, однако.


Я не знаю, подправил ты код или нет (там было две проблемы -- число нулей между соседними процессами и поиск двух нулей, в принципе, два нуля можно и не искать, а для того, чтобы шаг не был слишком большим, просто считать, что если найденное максимальное число нулей подряд в числе меньше 2, то считать, что оно равно 2 -- это не должно никак повлиять на быстродействие, но исключит возможность перепрыгнуть через искомое число). Но я не об этом. Есть ли у тебя какая статистика по тому, сколько времени тратится на умножение, а сколько -- на поиск нулей?

А вот зайца кому, зайца-выбегайца?!
Re[4]: A16
От: Beam Россия  
Дата: 15.02.09 17:15
Оценка:
Здравствуйте, vadimcher, Вы писали:

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

Код я поправил. Позже причешу и выложу сюда. Может кто продолжит.

V>Но я не об этом. Есть ли у тебя какая статистика по тому, сколько времени тратится на умножение, а сколько -- на поиск нулей?

Соотношение (Умножение:Поиск) очень близко к (2:1)
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Re[3]: k нулей
От: Beam Россия  
Дата: 15.02.09 17:15
Оценка:
Здравствуйте, nikholas, Вы писали:

N>Здравствуйте, Beam, Вы писали:


N>По поводу подсчета нулей:


N>Лучше сделать так: по числу x от 0000 до 9999 по таблице получаем zero-mask: на месте нулей — нули, на месте не-нулей — единицы (получаем число от 0 до 15)

N>для текущей позиции при подсчете нулей помним: максимальное число нулей найденое ранее (max, 0 — 19), число нулей на конце(curr, 0-19)
N>и далее по таблице [max, curr, zero-mask] получаем новые значения для max и curr
N>Для того чтоб не заморачиваться насчет конца числа curr может быть больше max, тогда в конце, после любого кол-ва финишных нулей, мы получаем точное значение max(главное в таблице сделать curr(19)->curr(19), а не то...).

Попробовал я такой вариант. Работает медленнее, почему — не знаю.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Best regards, Буравчик
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.