Multithreading 如何使用人造丝将一个大范围分割为多个范围块,并让每个线程在一个块中找到?

Multithreading 如何使用人造丝将一个大范围分割为多个范围块,并让每个线程在一个块中找到?,multithreading,rust,rayon,Multithreading,Rust,Rayon,我正在制作一个程序,通过并行化来强制输入密码。目前破解密码已经是纯文本了,我只是想强行破解它 我有一个名为generate\u char\u array()的函数,它基于一个整数种子,转换基数并返回一个u8字符片段以尝试检查。首先是字母表中的1个字符串,然后是2个字符串,以此类推 let found_string_index = (0..1e12 as u64).into_par_iter().find_any(|i| { let mut array = [0u8; 20]; l

我正在制作一个程序,通过并行化来强制输入密码。目前破解密码已经是纯文本了,我只是想强行破解它

我有一个名为
generate\u char\u array()
的函数,它基于一个整数种子,转换基数并返回一个
u8
字符片段以尝试检查。首先是字母表中的1个字符串,然后是2个字符串,以此类推

let found_string_index = (0..1e12 as u64).into_par_iter().find_any(|i| {
    let mut array = [0u8; 20];
    let bytes = generate_char_array(*i, &mut array);
    return &password_bytes == &bytes;
});
使用找到的字符串索引(或者种子整数),我可以生成找到的字符串

问题是,人造丝对我来说并行化的方式是将任意大整数范围拆分为
线程计数
-大切片(例如,对于4个线程,0..2.5e11,2.5e11..5e11等)。这是不好的,因为范围的末尾是任意超大的密码长度(10+,我不知道),而大多数密码(包括我倾向于尝试的固定“zzzzz”)要短得多,因此我得到的是第一个线程完成所有工作,而其余的线程只是浪费时间测试太长的密码和同步方式;因此,性能实际上比单线程慢


我如何将任意的大范围(实际上不必有结尾)分割成范围的块,并在块中找到每个线程?这将使不同线程中的工作线程实际有用。

这是我在评论中建议的版本

主循环是并行的,并且仅在每次尝试的第一个字节上。对于每个第一个字节,对剩余字节进行完整的暴力搜索

let matched_bytes = (0 .. 0xFFu8).into_par_iter().filter_map(|n| {
    let mut array = [0u8; 8];
    // the first digit is always the same in this run
    array[0] = n;
    // The highest byte is 0 because it's provided from the outer loop
    (0 ..= 0x0FFFFFFFFFFFFFFF as u64).into_iter().filter_map(|i| {
        // pass a slice so that the first byte is not affected
        generate_char_array(i, &mut array[1 .. 8]);
        if &password_bytes[..] == &array[0 .. password_bytes.len()] {
            Some(array.clone())
        } else {
            None
        }
    }).next()
}).find_any(|_| true);

println!("found = {:?}", matched_bytes);
而且,即使对于蛮力方法,这也可能是非常低效的

首先是字母表中的1个字符串,然后是2个字符串

您希望在数据处理上强加一些顺序,但人造丝的整个要点是并行进行

相反,使用常规迭代器按顺序增加长度,然后在特定长度内使用并行迭代器快速处理该长度的所有值

由于您没有为一个可运行的示例提供足够的代码,因此我做了这个粗略的近似,以显示这样一个解决方案的一般形状:

extern crate rayon;

use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::ops::RangeInclusive;

type Seed = u8;

const LENGTHS: RangeInclusive<usize> = 1..=3;
const SEEDS: RangeInclusive<Seed> = 0..=std::u8::MAX;

fn find<F>(test_password: F) -> Option<(usize, Seed)>
where
    F: Fn(usize, Seed) -> bool + Sync,
{
    // Rayon doesn't support RangeInclusive yet
    let seeds: Vec<_> = SEEDS.collect();

    // Step 1-by-1 through the lengths, sequentially
    LENGTHS.flat_map(|length| {
        // In parallel, investigate every value in this length
        // This doesn't do that, but it shows how the parallelization
        // would be introduced
        seeds
            .par_iter()
            .find_any(|&&seed| test_password(length, seed))
            .map(|&seed| (length, seed))
    }).next()
}

fn main() {
    let pass = find(|l, s| {
        println!("{}, {}", l, s);
        // Actually generate and check the password based on the search criteria
        l == 3 && s == 250
    });

    println!("Found password length and seed: {:?}", pass);
}
extern板条箱人造丝;
使用人造丝::iter::{into并行迭代器,并行迭代器};
使用std::ops::RangeInclusive;
种子类型=u8;
常数长度:RangeInclusive=1..=3;
常数种子:RangeInclusive=0..=std::u8::MAX;
fn find(测试密码:F)->选项
哪里
F:Fn(usize,Seed)->bool+Sync,
{
//人造丝还不支持Range
让种子:Vec=seeds.collect();
//按顺序逐1步通过长度
长度。平面图(|长度|{
//同时,调查此长度中的每个值
//这并没有做到这一点,但它显示了并行化是如何实现的
//将介绍
种子
.par_iter()
.find|u any(|&seed | test|u密码(长度,seed))
.map(|和种子|(长度,种子))
}).next()
}
fn main(){
让开=找到(| l,s |{
println!(“{},{}”,l,s);
//根据搜索条件实际生成并检查密码
l==3&&s==250
});
println!(“找到的密码长度和种子:{:?}”,pass);
}

这可能会在每段长度结束时“浪费”一点时间,因为平行线程会一个接一个地向下旋转,然后再向上旋转到下一段长度,但这似乎不是主要问题。

如果人造丝按照您所述分割切片,则应用简单的数学来平衡密码长度:

let found_string_index = (0..max_val as u64).into_par_iter().find_any(|i| {
    let mut array = [0u8; 20];
    let v = i/span + (i%span) * num_cpu;

    let bytes = generate_char_array(*v, &mut array);
    return &password_bytes == &bytes;
});
span
值取决于CPU数量(人造丝使用的线程数量),在您的情况下:

let num_cpu = 4;
let span = 2.5e11 as u64;
let max_val = span * num_cpu;

注意此方法的性能在很大程度上取决于人造丝如何在并行线程上执行序列分割。验证它是否如问题中所述工作。

您的代码存在一些问题。1) 您的数组是20个字节,但是
u64
包含8个字节。2) 您的迭代从0到1e12。要彻底获取所有
u64
值,您的范围应为
0。。0xFFFFFFFFFFFFFF
。执行此操作的一种方法可能是将其重新格式化为两个循环,外部(并行)循环是第一个字节,然后内部循环执行与现在完全相同的操作,只是使用外部循环的第一个数字。这样一来,每个第一个字节都会有一个线程,它们都会从短的开始,一直到长的。@PeterHall观察得很好,但这只是我写的一个很快的东西;理想情况下,我应该使用BigInts或其他类似的防锈剂。顺便说一句,请注意,在我的u64中,每个字节可以存储256种不同的数据组合,但在数组中,每个字节只能存储26种不同的数据组合,因为目前我只处理小写字母字符。同样在您的示例中,如果我让每个外部循环处理“第一个字节”,我不知道这是怎么回事,因为在一台16线程的Ryzen机器上,我最多有16个线程,而第一个字节可以有256个不同的组合。实际上,我可以在运行时确保线程数是2的幂次方,然后取那个数字,确保每个线程都以自己的数字从位级别开始,然后迭代每个线程,确保起始位相同。好主意!看起来这段代码行不通。请记住,总有26^长度的密码组合,因此根据长度的不同,组合的可能数量也不同。虽然我可以确定每个长度需要检查的范围,并且只需迭代这些范围;只要我能按长度顺序并行递增地测试密码,性能就会很好……顺便说一句,我不打算进行任何严格的顺序测试