[Python] Optimizing sieving code in the Self Initializing Quadratic Sieve for PyPy

Discussão em 'Python' iniciado por Stack, Janeiro 12, 2025.

  1. Stack

    Stack Membro Participativo

    I've coded up the Self Initializing Quadratic Sieve (SIQS) in Python, but it has been coded with respect to being as fast as possible in PyPy(not native Python).

    Here is the complete code:

    import logging
    import time
    import math
    from math import sqrt, ceil, floor, exp, log2, log, isqrt
    from rich.live import Live
    from rich.table import Table
    from rich.console import Console
    import random
    import sys

    LOWER_BOUND_SIQS = 1000
    UPPER_BOUND_SIQS = 4000
    logging.basicConfig(
    format='[%(levelname)s] %(asctime)s - %(message)s',
    level=logging.INFO
    )
    def get_gray_code(n):
    gray = [0] * (1 << (n - 1))
    gray[0] = (0, 0)
    for i in range(1, 1 << (n - 1)):
    v = 1
    j = i
    while (j & 1) == 0:
    v += 1
    j >>= 1
    tmp = i + ((1 << v) - 1)
    tmp >>= v
    if (tmp & 1) == 1:
    gray = (v - 1, -1)
    else:
    gray = (v - 1, 1)
    return gray

    MULT_LIST = [
    1, 2, 3, 5, 7, 9, 10, 11, 13, 14,
    15, 17, 19, 21, 23, 25, 29, 31,
    33, 35, 37, 39, 41, 43, 45,
    47, 49, 51, 53, 55, 57, 59, 61, 63,
    65, 67, 69, 71, 73, 75, 77, 79, 83,
    85, 87, 89, 91, 93, 95, 97, 101, 103, 105, 107,
    109, 111, 113, 115, 119, 121, 123, 127, 129, 131, 133,
    137, 139, 141, 143, 145, 147, 149, 151, 155, 157, 159,
    161, 163, 165, 167, 173, 177, 179, 181, 183, 185, 187,
    191, 193, 195, 197, 199, 201, 203, 205, 209, 211, 213,
    215, 217, 219, 223, 227, 229, 231, 233, 235, 237, 239,
    241, 249, 251, 253, 255
    ]

    def create_table(relations, target_relations, num_poly, start_time):
    end = time.time()
    elapsed = end - start_time

    relations_per_second = len(relations) / elapsed if elapsed > 0 else 0
    poly_per_second = num_poly / elapsed if elapsed > 0 else 0
    percent = (len(relations) / target_relations) * 100 if target_relations > 0 else 0
    percent_per_second = percent / elapsed if elapsed > 0 else 0
    remaining_percent = 100.0 - percent
    seconds = int(remaining_percent / percent_per_second) if percent_per_second > 0 else 0
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)

    table = Table(title="Processing Status")
    table.add_column("Metric", style="cyan", no_wrap=True)
    table.add_column("Value", style="magenta")

    table.add_row("Relations per second", f"{relations_per_second:,.2f}")
    table.add_row("Poly per second", f"{poly_per_second:,.2f}")
    table.add_row("Percent", f"{percent:,.2f}%")
    table.add_row("Percent per second", f"{percent_per_second:,.4f}%")
    table.add_row("Estimated Time", f"{h:d}:{m:02d}:{s:02d}")

    return table

    class QuadraticSieve:
    def __init__(self, M, B=None, T=2, prime_limit=20, eps=30, lp_multiplier=20, multiplier=None):
    self.logger = logging.getLogger(__name__)
    self.prime_log_map = {}
    self.root_map = {}

    self.M = M
    self.B = B
    self.T = T
    self.prime_limit = prime_limit
    self.eps = eps
    self.lp_multiplier = lp_multiplier
    self.multiplier = multiplier

    self.console = Console()

    print(f"B: {B}")
    print(f"M: {M}")
    print(f"prime_limit: {prime_limit}")
    print(f"eps: {eps}")
    print(f"lp_multiplier: {lp_multiplier}")

    @staticmethod
    def gcd(a, b):
    a, b = abs(a), abs(b)
    while a:
    a, b = b % a, a
    return b

    @staticmethod
    def legendre(n, p):
    val = pow(n, (p - 1) // 2, p)
    return val - p if val > 1 else val

    @staticmethod
    def jacobi(a, m):
    a = a % m
    t = 1
    while a != 0:
    while a % 2 == 0:
    a //= 2
    if m % 8 in [3, 5]:
    t = -t
    a, m = m, a
    if a % 4 == 3 and m % 4 == 3:
    t = -t
    a %= m
    return t if m == 1 else 0

    @staticmethod
    def modinv(n, p):
    n = n % p
    x, u = 0, 1
    while n:
    x, u = u, x - (p // n) * u
    p, n = n, p % n
    return x

    def factorise_fast(self, value, factor_base):
    factors = set()
    if value < 0:
    factors ^= {-1}
    value = -value
    for factor in factor_base[1:]:
    while value % factor == 0:
    factors ^= {factor}
    value //= factor
    return factors, value

    @staticmethod
    def tonelli_shanks(a, p):
    a %= p
    if p % 8 in [3, 7]:
    x = pow(a, (p + 1) // 4, p)
    return x, p - x

    if p % 8 == 5:
    x = pow(a, (p + 3) // 8, p)
    if pow(x, 2, p) != a % p:
    x = (x * pow(2, (p - 1) // 4, p)) % p
    return x, p - x

    d = 2
    symb = 0
    while symb != -1:
    symb = QuadraticSieve.jacobi(d, p)
    d += 1
    d -= 1

    n = p - 1
    s = 0
    while n % 2 == 0:
    n //= 2
    s += 1
    t = n

    A = pow(a, t, p)
    D = pow(d, t, p)
    m = 0
    for i in range(s):
    i1 = pow(2, s - 1 - i)
    i2 = (A * pow(D, m, p)) % p
    i3 = pow(i2, i1, p)
    if i3 == p - 1:
    m += pow(2, i)
    x = (pow(a, (t + 1) // 2, p) * pow(D, m // 2, p)) % p
    return x, p - x

    @staticmethod
    def prime_sieve(n):
    sieve = [True] * (n + 1)
    sieve[0], sieve[1] = False, False

    for i in range(2, int(n**0.5) + 1):
    if sieve:
    for j in range(i * 2, n + 1, i):
    sieve[j] = False

    return [i for i, is_prime in enumerate(sieve) if is_prime]

    def find_b(self, N):
    x = ceil(exp(0.5 * sqrt(log(N) * log(log(N)))))
    return x

    def choose_multiplier(self, N, B):
    prime_list = self.prime_sieve(B)
    if self.multiplier is not None:
    self.logger.info("Using multiplier k = %d", self.multiplier)
    return prime_list
    NUM_TEST_PRIMES = 300
    LN2 = math.log(2)
    num_primes = min(len(prime_list), NUM_TEST_PRIMES)
    log2n = math.log(N)
    scores = [0.0 for _ in MULT_LIST]
    num_multipliers = 0

    for i, curr_mult in enumerate(MULT_LIST):
    knmod8 = (curr_mult * (N % 8)) % 8
    logmult = math.log(curr_mult)
    scores = 0.5 * logmult

    if knmod8 == 1:
    scores -= 2 * LN2
    elif knmod8 == 5:
    scores -= LN2
    elif knmod8 in (3, 7):
    scores -= 0.5 * LN2

    num_multipliers += 1

    for i in range(1, num_primes):
    prime = prime_list
    contrib = math.log(prime) / (prime - 1)
    modp = N % prime

    for j in range(num_multipliers):
    curr_mult = MULT_LIST[j]
    knmodp = (modp * curr_mult) % prime

    if knmodp == 0 or self.legendre(knmodp, prime) == 1:
    if knmodp == 0:
    scores[j] -= contrib
    else:
    scores[j] -= 2 * contrib

    best_score = float('inf')
    best_mult = 1

    for i in range(num_multipliers):
    if scores < best_score:
    best_score = scores
    best_mult = MULT_LIST

    self.multiplier = best_mult
    self.logger.info("Using multiplier k = %d", best_mult)
    return prime_list

    def get_smooth_b(self, N, B, prime_list):
    factor_base = [-1, 2]
    self.prime_log_map[2] = 1
    for p in prime_list[1:]:
    if self.legendre(N, p) == 1:
    factor_base.append(p)
    self.prime_log_map[p] = round(log2(p))
    self.root_map[p] = self.tonelli_shanks(N, p)

    return factor_base

    def decide_bound(self, N, B=None):
    if B is None:
    B = self.find_b(N)
    self.B = B
    self.logger.info("Using B = %d", B)
    return B

    def build_factor_base(self, N, B, prime_list):
    fb = self.get_smooth_b(N, B, prime_list)
    self.logger.info("Factor base size: %d", len(fb))
    return fb

    def new_poly_a(self, factor_base, N, M, poly_a_list):
    small_B = 1024
    lower_polypool_index = 2
    upper_polypool_index = small_B - 1
    poly_low_found = False
    for i in range(small_B):
    if factor_base > LOWER_BOUND_SIQS and not poly_low_found:
    lower_polypool_index = i
    poly_low_found = True
    if factor_base > UPPER_BOUND_SIQS:
    upper_polypool_index = i - 1
    break
    # Compute target_a and bit threshold
    target_a = int(math.sqrt(2 * N) / M)
    target_mul = 0.9
    target_bits = int(target_a.bit_length() * target_mul)
    too_close = 10
    close_range = 5
    min_ratio = LOWER_BOUND_SIQS

    while True:
    poly_a = 1
    afact = []
    qli = []
    while True:
    found_a_factor = False
    while(found_a_factor == False):
    randindex = random.randint(lower_polypool_index, upper_polypool_index)
    potential_a_factor = factor_base[randindex]
    found_a_factor = True
    if potential_a_factor in afact:
    found_a_factor = False

    poly_a = poly_a * potential_a_factor
    afact.append(potential_a_factor)
    qli.append(randindex)

    j = target_a.bit_length() - poly_a.bit_length()
    if j < too_close:
    poly_a = 1
    s = 0
    afact = []
    qli = []
    continue
    elif j < (too_close + close_range):
    break

    a1 = target_a // poly_a
    if a1 < min_ratio:
    continue

    mindiff = 100000000000000000
    randindex = 0

    for i in range(small_B):
    if abs(a1 - factor_base) < mindiff:
    mindiff = abs(a1 - factor_base)
    randindex = i

    found_a_factor = False
    while not found_a_factor:
    potential_a_factor = factor_base[randindex]
    found_a_factor = True
    if potential_a_factor in afact:
    found_a_factor = False
    if not found_a_factor:
    randindex += 1

    if randindex > small_B:
    continue

    poly_a = poly_a * factor_base[randindex]
    afact.append(factor_base[randindex])
    qli.append(randindex)

    diff_bits = (target_a - poly_a).bit_length()

    if diff_bits < target_bits:
    if poly_a in poly_a_list:
    if target_bits > 1000:
    print("SOMETHING WENT WRONG")
    sys.exit()
    target_bits += 1
    continue
    else:
    break

    poly_a_list.append(poly_a)
    return poly_a, sorted(qli), set(afact)

    def generate_first_polynomial(self, factor_base, N, M, poly_a_list):
    a, qli, factors_a = self.new_poly_a(factor_base, N, M, poly_a_list)
    s = len(qli)
    B = []
    for l in range(s):
    p = factor_base[qli[l]]
    r1 = self.root_map[p][0]
    aq = a // p
    invaq = self.modinv(aq, p)
    gamma = r1 * invaq % p
    if gamma > p // 2:
    gamma = p - gamma
    B.append(aq * gamma)
    b = sum(B) % a
    c = (b * b - N) // a
    soln_map = {}
    Bainv = {}
    for p in factor_base:
    Bainv[p] = []
    if a % p == 0 or p == 2:
    continue

    ainv = self.modinv(a, p)
    # store bainv
    for j in range(s):
    Bainv[p].append((2 * B[j] * ainv) % p)

    # store roots
    r1, r2 = self.root_map[p]
    r1 = ((r1 - b) * ainv) % p
    r2 = ((r2 - b) * ainv) % p
    soln_map[p] = [r1, r2]

    return a, b, c, B, Bainv, soln_map, s, factors_a


    def sieve(self, N, B, factor_base, M):
    # ------------------------------------------------
    # 1) TIMING
    # ------------------------------------------------
    start = time.time()

    # ------------------------------------------------
    # 2) FACTOR BASE & RELATED
    # ------------------------------------------------
    fb_len = len(factor_base)
    fb_map = {val: i for i, val in enumerate(factor_base)}
    target_relations = fb_len + self.T
    large_prime_bound = B * self.lp_multiplier

    # ------------------------------------------------
    # 3) THRESHOLD & MISC
    # ------------------------------------------------
    threshold = int(math.log2(M * math.sqrt(N)) - self.eps)
    lp_found = 0
    ind = 1
    matrix = [0] * fb_len
    relations = []
    roots = []
    partials = {}
    num_poly = 0
    interval_size = 2 * M + 1
    grays = get_gray_code(20)
    poly_a_list = []
    poly_ind = 0
    sieve_values = [0] * interval_size
    r1 = 0
    r2 = 0
    def process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a):
    nonlocal ind
    val = sieve_values[x]
    sieve_values[x] = 0
    lpf = 0
    if val > threshold:
    xval = x - M
    relation = a * xval + b
    poly_val = a * xval * xval + 2 * b * xval + c

    local_factors, value = self.factorise_fast(poly_val, factor_base)
    local_factors ^= factors_a


    if value != 1:
    if value < large_prime_bound:
    if value in partials:
    rel, lf, pv = partials[value]
    relation *= rel
    local_factors ^= lf
    poly_val *= pv
    lpf = 1
    else:
    partials[value] = (relation, local_factors, poly_val * a)
    return 0
    else:
    return 0

    for fac in local_factors:
    idx = fb_map[fac]
    matrix[idx] |= ind
    ind = ind + ind
    relations.append(relation)
    roots.append(poly_val * a)
    return lpf

    with Live(console=self.console) as live:
    while len(relations) < target_relations:
    if num_poly % 10 == 0:
    live.update(create_table(relations, target_relations, num_poly, start))

    if poly_ind == 0:
    a, b, c, B, Bainv, soln_map, s, factors_a = self.generate_first_polynomial(factor_base, N, M, poly_a_list)
    end = 1 << (s - 1)
    poly_ind += 1
    else:
    v, e = grays[poly_ind]
    b = (b + 2 * e * B[v])
    c = (b * b - N) // a
    poly_ind += 1
    if poly_ind == end:
    poly_ind = 0
    v, e = grays[poly_ind] # v, e for next iteration
    for p in factor_base:
    if p < self.prime_limit or a % p == 0:
    continue
    log_p = self.prime_log_map[p]
    r1, r2 = soln_map[p]
    soln_map[p][0] = (r1 - e * Bainv[p][v]) % p
    soln_map[p][1] = (r2 - e * Bainv[p][v]) % p

    amx = r1 + M
    bmx = r2 + M

    apx = amx - p
    bpx = bmx - p
    k = p

    while k < M:
    sieve_values[apx + k] += log_p
    sieve_values[bpx + k] += log_p
    sieve_values[amx - k] += log_p
    sieve_values[bmx - k] += log_p
    k += p
    num_poly += 1

    x = 0
    while x < 2 * M - 6:
    # for some reason need to do all this for max performance gain in PyPy3
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1

    print(f"\n{num_poly} polynomials sieved")
    print(f"{lp_found} relations from partials")
    print(f"{target_relations - lp_found} normal smooth relations")
    print(f"{target_relations} total relations\n")
    return matrix, relations, roots

    def solve_bits(self, matrix, n):
    self.logger.info("Solving linear system in GF(2).")
    lsmap = {lsb: 1 << lsb for lsb in range(n)}

    # GAUSSIAN ELIMINATION
    m = len(matrix)
    marks = []
    cur = -1
    # m -> number of primes in factor base
    # n -> number of smooth relations
    mark_mask = 0
    for row in matrix:
    if cur % 100 == 0:
    print("", end=f"{cur, m}\r")
    cur += 1
    lsb = (row & -row).bit_length() - 1
    if lsb == -1:
    continue
    marks.append(n - lsb - 1)
    mark_mask |= 1 << lsb
    for i in range(m):
    if matrix & lsmap[lsb] and i != cur:
    matrix ^= row
    marks.sort()
    # NULL SPACE EXTRACTION
    nulls = []
    free_cols = [col for col in range(n) if col not in marks]
    k = 0
    for col in free_cols:
    shift = n - col - 1
    val = 1 << shift
    fin = val
    for v in matrix:
    if v & val:
    fin |= v & mark_mask
    nulls.append(fin)
    k += 1
    if k == self.T:
    break
    return nulls

    def extract_factors(self, N, relations, roots, null_space):
    n = len(relations)
    for vector in null_space:
    prod_left = 1
    prod_right = 1
    for idx in range(len(relations)):
    bit = vector & 1
    vector = vector >> 1
    if bit == 1:
    prod_left *= relations[idx]
    prod_right *= roots[idx]
    idx += 1

    sqrt_right = isqrt(prod_right)
    prod_left = prod_left % N
    sqrt_right = sqrt_right % N
    factor_candidate = self.gcd(N, prod_left - sqrt_right)

    if factor_candidate not in (1, N):
    other_factor = N // factor_candidate
    self.logger.info("Found factors: %d, %d", factor_candidate, other_factor)
    return factor_candidate, other_factor

    return 0, 0

    def factor(self, N, B=None):
    overall_start = time.time()
    self.logger.info("========== Quadratic Sieve V4 Start ==========")
    self.logger.info("Factoring N = %d", N)

    step_start = time.time()
    B = self.decide_bound(N, self.B)
    step_end = time.time()
    self.logger.info("Step 1 (Decide Bound) took %.3f seconds", step_end - step_start)

    step_start = time.time()
    prime_list = self.choose_multiplier(N, self.B)
    step_end = time.time()
    self.logger.info("Step 2 (Choose Multiplier) took %.3f seconds", step_end - step_start)
    kN = self.multiplier * N
    if kN.bit_length() < 140:
    LOWER_BOUND_SIQS = 3

    step_start = time.time()
    factor_base = self.build_factor_base(kN, B, prime_list)
    step_end = time.time()
    self.logger.info("Step 3 (Build Factor Base) took %.3f seconds", step_end - step_start)

    step_start = time.time()
    matrix, relations, roots = self.sieve(kN, B, factor_base, self.M)
    step_end = time.time()
    self.logger.info("Step 4 (Sieve Interval) took %.3f seconds", step_end - step_start)

    n = len(relations)
    step_start = time.time()
    null_space = self.solve_bits(matrix, n)
    step_end = time.time()
    self.logger.info("Step 5 (Solve Dependencies) took %.3f seconds", step_end - step_start)

    step_start = time.time()
    f1, f2 = self.extract_factors(N, relations, roots, null_space)
    step_end = time.time()
    self.logger.info("Step 6 (Extract Factors) took %.3f seconds", step_end - step_start)

    if f1 and f2:
    self.logger.info("Quadratic Sieve successful: %d * %d = %d", f1, f2, N)
    else:
    self.logger.warning("No non-trivial factors found with the current settings.")

    overall_end = time.time()
    self.logger.info("Total time for Quadratic Sieve: %.10f seconds", overall_end - overall_start)
    self.logger.info("========== Quadratic Sieve End ==========")

    return f1, f2

    if __name__ == '__main__':

    ## 60 digit number
    #N = 373784758862055327503642974151754627650123768832847679663987
    #qs = QuadraticSieve(B=111000, M=400000, T=10, prime_limit=45, eps=34, lp_multiplier=20000)

    ### 70 digit number
    N = 3605578192695572467817617873284285677017674222302051846902171336604399
    qs = QuadraticSieve(B=300000, M=350000, prime_limit=47, eps=40, T=10, lp_multiplier=256)

    ## 80 digit number
    #N = 4591381393475831156766592648455462734389 * 1678540564209846881735567157366106310351
    #qs = QuadraticSieve(B=700_000, M=600_000, prime_limit=52, eps=45, T=10, lp_multiplier=256)

    factor1, factor2 = qs.factor(N)



    Now, the main running time in the comes from the following section which is where basically a giant sieving process:

    def sieve(self, N, B, factor_base, M):
    start = time.time()

    fb_len = len(factor_base)
    fb_map = {val: i for i, val in enumerate(factor_base)}
    target_relations = fb_len + self.T
    large_prime_bound = B * self.lp_multiplier


    threshold = int(math.log2(M * math.sqrt(N)) - self.eps)
    lp_found = 0
    ind = 1

    matrix = [0] * fb_len
    relations = []
    roots = []
    partials = {}

    num_poly = 0
    interval_size = 2 * M + 1
    grays = get_gray_code(20)
    poly_a_list = []
    poly_ind = 0

    sieve_values = [0] * interval_size
    r1 = 0
    r2 = 0

    def process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a):
    nonlocal ind
    val = sieve_values[x]
    sieve_values[x] = 0
    lpf = 0
    if val > threshold:
    xval = x - M
    relation = a * xval + b
    poly_val = a * xval * xval + 2 * b * xval + c

    local_factors, value = self.factorise_fast(poly_val, factor_base)
    local_factors ^= factors_a


    if value != 1:
    if value < large_prime_bound:
    if value in partials:
    rel, lf, pv = partials[value]
    relation *= rel
    local_factors ^= lf
    poly_val *= pv
    lpf = 1
    else:
    partials[value] = (relation, local_factors, poly_val * a)
    return 0
    else:
    return 0

    for fac in local_factors:
    idx = fb_map[fac]
    matrix[idx] |= ind
    ind = ind + ind
    relations.append(relation)
    roots.append(poly_val * a)
    return lpf

    with Live(console=self.console) as live:
    while len(relations) < target_relations:
    if num_poly % 10 == 0:
    live.update(create_table(relations, target_relations, num_poly, start))

    if poly_ind == 0:
    a, b, c, B, Bainv, soln_map, s, factors_a = self.generate_first_polynomial(factor_base, N, M, poly_a_list)
    end = 1 << (s - 1)
    poly_ind += 1
    else:
    v, e = grays[poly_ind]
    b = (b + 2 * e * B[v])
    c = (b * b - N) // a
    poly_ind += 1
    if poly_ind == end:
    poly_ind = 0
    v, e = grays[poly_ind] # v, e for next iteration
    for p in factor_base:
    if p < self.prime_limit or a % p == 0:
    continue
    log_p = self.prime_log_map[p]
    r1, r2 = soln_map[p]
    soln_map[p][0] = (r1 - e * Bainv[p][v]) % p
    soln_map[p][1] = (r2 - e * Bainv[p][v]) % p

    amx = r1 + M
    bmx = r2 + M

    apx = amx - p
    bpx = bmx - p
    k = p

    while k < M:
    sieve_values[apx + k] += log_p
    sieve_values[bpx + k] += log_p
    sieve_values[amx - k] += log_p
    sieve_values[bmx - k] += log_p
    k += p
    num_poly += 1

    x = 0
    while x < 2 * M - 6:
    # for some reason need to do all this for max performance gain in PyPy3
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1
    lp_found += process_sieve_value(x, sieve_values, partials, relations, roots, a, b, c, factors_a)
    x += 1

    print(f"\n{num_poly} polynomials sieved")
    print(f"{lp_found} relations from partials")
    print(f"{target_relations - lp_found} normal smooth relations")
    print(f"{target_relations} total relations\n")
    return matrix, relations, roots


    I have "optimized" the code as much as I can so far, and it runs pretty fast in PyPy, but I am wondering if there is anything I am missing that can tweak out more performance gain. I haven't really been able to get anything meaningful out of profiling the code because of the way that PyPy works, but through a bit of testing have made various improvements that have cut down the time a lot like some loop unrolling, precision reduction, and the way I work with the arrays.

    Unfortunately, this sieving code is still not fast enough to be feasible in factoring the size of numbers which I'm targeting(100 to 115 digit semiprimes). By my initial estimates with semi-decent parameter selection and without multiprocessing, the sieving code itself would taken around 70 hours when compiled using PyPy3 on my device.

    Is there anything I can do to better optimize this code for the PyPy JIT?

    Continue reading...

Compartilhe esta Página