C# - 提高搜索时的性能

Posted

技术标签:

【中文标题】C# - 提高搜索时的性能【英文标题】:C# - Improve performace when searching 【发布时间】:2018-12-10 20:30:59 【问题描述】:

我在 txt 文件中有一个包含 15000000 个用户名的列表,我编写了一个方法来创建大脑钱包,检查是否有任何地址包含 600 个地址的列表。差不多就是这样

private static List<string> userList = new List<string>(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoUser-workspace-db.txt"));
private static List<string> enterpriseUserList = new List<string>(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoEnterpriseUser-local-db.txt"));
foreach (var i in userList)
 
    userid = ToAddress(i);
    if (enterpriseUserList.Contains(userid))
        Console.WriteLine(i,userid);        
    
    private string ToAddress(string username)
    
        string bitcoinAddress = BitcoinAddress.GetBitcoinAdressEncodedStringFromPublicKey(new PrivateKey(Globals.ProdDumpKeyVersion, new SHA256Managed().ComputeHash(UTF8Encoding.UTF8.GetBytes(username), 0, UTF8Encoding.UTF8.GetBytes(username).Length), false).PublicKey);     
    

ToAddrsess 方法将用户名哈希为 SHA256 字符串,获取其公钥并将其转换为如下地址:

15hDBtLpQfcbrrAFupWjgN5ieHeEBd8mbu

这段代码很糟糕,运行速度很慢,每秒处理大约 200 行数据。所以我尝试使用多线程来改进它

private static void CheckAddress(string username)
                      
    var userid = ToAddress(username);
    if (enterpriseUserList.Contains(userid))
    
        Console.WriteLine(i,userid);        
                

private static void Parallel() 

    List<string> items = new List<string>(File.ReadLines(@"C:\Users\Erik\Desktop\InfernoUser-workspace-db.txt"));
    ParallelOptions check = new ParallelOptions()  MaxDegreeOfParallelism = 100 ;
    Parallel.ForEach<string>(items, check, line =>
    
        CheckAddress(line);
    );

这并没有太大帮助。有人可以建议如何即兴创作吗?与在 CPU 上运行的 vanitygen 相比,它每秒可以处理 4-500k 个地址。怎么会有这么大的区别?

【问题讨论】:

Contains 进行线性搜索,您的算法基本上运行在 O(N×M) 中,如果它可以使用某种索引会快很多。 @Bart Friederichs 你能说得更具体点吗? 您可以解码企业列表中的 600 并进行比较,而不是对用户列表中的 1500 万进行编码吗? @Jimmy 这不是 SHA256 的工作方式,您不能保留它。我从userList中随机选择了600个用户名,转换成地址做企业列表 好的,错过了 sha256 部分 【参考方案1】:

您可以尝试使用带有 key=userid 的 Dictionary,以防止每次迭代都按列表搜索

var dict = new ConcurrentDictionary<string, string>(100, userList.Count);

        userList.AsParallel().ForAll(item => 
        
            dict.AddOrUpdate(ToAddress(item), item, (key,value)=>return value;);
        );

        enterpriseUserList.AsParallel().ForAll(x =>
        
            if (dict.ContainsKey(x))
             Console.WriteLine(dict[x]); 
        );

【讨论】:

谢谢,如果你对加密货币有任何了解,你能知道有什么方法可以更快地创建大脑钱包。我的代码大约需要 200 毫秒来创建一个 AddOrUpdate 有错误,它说我不能接受 2 个参数。我该如何解决这个问题? 第二个.AsParallel() 可能浪费多于帮助。 @RandRandom 你能推荐其他解决方案吗?我只是切换到 MikkaRin 方式,它使我的代码每秒运行更多大约 260 行用户名。我猜是什么 @HuangLee - 不争论第一个 .AsParallel() 代码块女巫会带来一些好处,但第二个不会表现良好 - Console.WriteLine 部分将同步并阻止并行执行 - 另外没有任何反应那里需要并行执行。一个普通的foreach 会表现得更好。 - 根据对Console.WriteLine 的调用次数,您应该考虑切换到字符串生成器并只调用一次Console.WriteLine(stringBuilder.ToString());【参考方案2】:

在寻找效率低下时,主要的危险信号之一是重复的函数调用。您拨打GetBytes 两次。将它放入一个单独的变量中并调用一次应该会有所帮助。

private string ToAddress(string username)

    var userNameAsBytes = UTF8Encoding.UTF8.GetBytes(username);
    string bitcoinAddress = BitcoinAddress.GetBitcoinAdressEncodedStringFromPublicKey(new PrivateKey(Globals.ProdDumpKeyVersion, new SHA256Managed().ComputeHash(userNameAsBytes, 0, userNameAsBytes.Length), false).PublicKey);     

【讨论】:

我只是用秒表测试它,它几乎是一样的:|【参考方案3】:

你可以在这里进行一些操作

    List 更新为HashSet。它将显着执行Contains 操作。我确信这是这个代码库中最慢的情况。 private static List&lt;string&gt; enterpriseUserList = new List&lt;string&gt;(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoEnterpriseUser-local-db.txt")); 改成 private static HashSet&lt;string&gt; enterpriseUserList = new HashSet&lt;string&gt;(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoEnterpriseUser-local-db.txt")); 不要使用ParallelOptions check = new ParallelOptions() MaxDegreeOfParallelism = 100 ; 这种优化会增加上下文切换并降低性能。 使用Partitioner.Create优化Parallel.ForEach

也许这就是我能给你的建议。

    private static List<string> userList = new List<string>(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoUser-workspace-db.txt"));
    private static HashSet<string> enterpriseUserList = new HashSet<string>(File.ReadAllLines(@"C:\Users\Erik\Desktop\InfernoEnterpriseUser-local-db.txt"));

 [MethodImpl(MethodImplOptions.AggressiveInlining)]
   private static void CheckAddress(int id,string username)
                      
    var userid = ToAddress(username);
    if (enterpriseUserList.Contains(userid))
    
       // todo
                



private static void Parallel() 

    var ranges = Partitioner.Create(0,userList.Count);
    Parallel.ForEach(ranges ,(range)=>
     for(int i=range.Item1;i<range.Item2;i++)
              CheckAddress(i,userList[i])               
     


【讨论】:

以上是关于C# - 提高搜索时的性能的主要内容,如果未能解决你的问题,请参考以下文章

C# 提高数组查找循环性能

提高使用 CreateDocumentQuery 和 ExecuteNextAsync 时的性能

将 7z 文件提取到目录 c# 时的性能问题

ConcurrentHashMap是如何提高并发时的吞吐性能

提高C#质量与性能

通过 C# 导入 Excel 文件时的性能瓶颈