首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

朋友吐槽我为什么这么傻不在源生成器中用string.GetHashCode, 而要用一个不够优化的hash方法

编程知识
2024年08月10日 21:06

明明有更好的hash方法

有位朋友对我吐槽前几天我列举的在源生成器的生成db映射实体的优化点 提前生成部分 hashcode 进行比较

所示代码

public static void GenerateReadTokens(this IDataReader reader, Span<int> s)
{
    for (int i = 0; i < reader.FieldCount; i++)
    {
        var name = reader.GetName(i);
        var type = reader.GetFieldType(i);
        switch (EntitiesGenerator.SlowNonRandomizedHash(name))
        {
            
            case 742476188U:
                s[i] = type == typeof(int) ? 1 : 2; 
                break;

            case 2369371622U:
                s[i] = type == typeof(string) ? 3 : 4; 
                break;

            case 1352703673U:
                s[i] = type == typeof(float) ? 5 : 6; 
                break;

            default:
                break;
        }
    }
}

这里为什么不用 string.GetHashCode, 而要用 SlowNonRandomizedHash(name), 有更好的方法不用,真是傻

当时俺也只能 囧 着脸给ta解释 string.GetHashCode真的没办法用,

可惜口头几句解释再多,一时也无法摆脱ta鄙视的目光

只有在此多写几句“狡辩”

“狡辩”

首先其实NormalizedHash 性能很强的,其实现如下

public static uint SlowNonRandomizedHash(this string? value)
{
    uint hash = 0;
    if (!string.IsNullOrEmpty(value))
    {
        hash = 2166136261u;
        foreach (char c in value!)
        {
            hash = (char.ToLowerInvariant(c) ^ hash) * 16777619;
        }
    }
    return hash;
}

但是不管性能强不强,也不是只能用这个方法的原因

其实真实原因很多人都知道,都是大家的默认常识了:net code string.GetHashCode是随机的,多次运行程序,同一个字符串可能会在每次运行都有不同的哈希值

比如 18年的文章 Why is string.GetHashCode() different each time I run my program in .NET Core?

这里简单复述一下原文内容

以这个非常简单的程序为例,它连续两次调用一个字符串GetHashCode()

using System;

static class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!".GetHashCode());
        Console.WriteLine("Hello World!".GetHashCode());
    }
}

如果在 .NET Framework 上运行此程序,则每次运行该程序时,都会获得相同的值:

> dotnet run -c Release -f net471
-1989043627
-1989043627

> dotnet run -c Release -f net471
-1989043627
-1989043627

> dotnet run -c Release -f net471
-1989043627
-1989043627

相反,如果为 .NET Core 编译同一程序,则在同一程序执行中每次调用都会获得相同的值,但对于不同的程序执行,将获得不同的值:GetHashCode()

> dotnet run -c Release -f netcoreapp2.1
-1105880285
-1105880285

> dotnet run -c Release -f netcoreapp2.1
1569543669
1569543669

> dotnet run -c Release -f netcoreapp2.1
-1477343390
-1477343390

努力查找之后,在微软官方文档给出过使用GetHashCode()方法的建议。其明确提示,不应将GetHashCode()方法产生的hash值当作为相同能持久化的值使用。

The hash code itself is not guaranteed to be stable. Hash codes for identical strings can differ across .NET implementations, across .NET versions, and across .NET platforms (such as 32-bit and 64-bit) for a single version of .NET. In some cases, they can even differ by application domain. This implies that two subsequent runs of the same program may return different hash codes.

为什么要用随机化的 hash?

Stephen Toub 在一个issue 中提到了这个问题的答案:

Q: Why .NET Core utilize randomized string hashing?
问:为什么 .NET Core 使用随机字符串哈希?
A: Security, prevention against DoS attacks, etc.
A:安全性、防止 DoS 攻击等。

原文很详细的解释有关安全的内容,这里就不作详细复述了

那么有没有更好的 hash 方法呢?

当然肯定是有的,string 类内部其实就有,

感兴趣的童鞋可以阅读源码 https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs#L923

里面 大小写敏感和不敏感都有实现, 其代码比上面18年文章列举的方法还有更多性能优化

不过内部方法,我们没有办法可以直接使用

但是呢? 我们有黑魔法可以直接使用

public static partial class StringHashing
{
    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetNonRandomizedHashCodeOrdinalIgnoreCase")]
    public static extern int Hash(this string c);
}

比较一下

我们都写到这里了,不比一下性能,大家肯定不服气

来一段简单的比较

[ShortRunJob, MemoryDiagnoser, Orderer(summaryOrderPolicy: SummaryOrderPolicy.FastestToSlowest), GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
public class StringHashingBenchmarks
{
    [Params(0, 1, 10, 100)]
    public int Count { get; set; }

    public string Str { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        var s = string.Join("", Enumerable.Repeat("_", Count));
        var b = Encoding.UTF8.GetBytes(s);
        Random.Shared.NextBytes(b);
        Str = Encoding.UTF8.GetString(b);
    }

    [Benchmark(Baseline = true)]
    public int GetHashCode()
    {
        return Str.GetHashCode();
    }

    [Benchmark]
    public uint SlowNonRandomizedHash()
    {
        return Str.SlowNonRandomizedHash();
    }

    [Benchmark]
    public int NonRandomizedHash()
    {
        return Str.Hash();
    }
}

结果


BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
13th Gen Intel Core i9-13900KF, 1 CPU, 32 logical and 24 physical cores
.NET SDK 9.0.100-preview.6.24328.19
  [Host]   : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
  ShortRun : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Count Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
SlowNonRandomizedHash 0 0.3286 ns 0.0727 ns 0.0040 ns 0.69 0.01 - NA
GetHashCode 0 0.4751 ns 0.1093 ns 0.0060 ns 1.00 0.00 - NA
NonRandomizedHash 0 0.6614 ns 0.0339 ns 0.0019 ns 1.39 0.02 - NA
GetHashCode 1 0.5686 ns 0.0881 ns 0.0048 ns 1.00 0.00 - NA
NonRandomizedHash 1 0.6559 ns 0.0254 ns 0.0014 ns 1.15 0.01 - NA
SlowNonRandomizedHash 1 7.3752 ns 0.2379 ns 0.0130 ns 12.97 0.11 - NA
GetHashCode 10 3.1627 ns 0.2081 ns 0.0114 ns 1.00 0.00 - NA
NonRandomizedHash 10 16.1921 ns 1.1773 ns 0.0645 ns 5.12 0.02 - NA
SlowNonRandomizedHash 10 44.4825 ns 2.8742 ns 0.1575 ns 14.06 0.01 - NA
GetHashCode 100 40.4233 ns 0.7217 ns 0.0396 ns 1.00 0.00 - NA
NonRandomizedHash 100 110.2494 ns 13.1581 ns 0.7212 ns 2.73 0.02 - NA
SlowNonRandomizedHash 100 362.0329 ns 11.0681 ns 0.6067 ns 8.96 0.02 - NA

当然,我们比较的 hash code 是大小写敏感的, 而其他两个是大小写不敏感的,

但是其差距都非常小,所以可以说都是很强的方法了

可惜 UnsafeAccessor 这些黑魔法无法在源生成器中使用

From:https://www.cnblogs.com/fs7744/p/18352853
本文地址: http://shuzixingkong.net/article/975
0评论
提交 加载更多评论
其他文章 [考试记录] 2024.8.10 csp-s 模拟赛18
80 + 20 + 0 + 70 = 170 第三题应该有 10 分暴力的,但我没打。 T1 星际旅行 题面翻译 总共有n个节点,m条路径,要求其中m-2条路径走两遍,剩下2条路径仅走一遍,问不同的路径总数有多少,如果仅走一遍的两条边不同则将这两条路径视为不同。 样例 #1 样例输入 #1 5 4
[考试记录] 2024.8.10 csp-s 模拟赛18 [考试记录] 2024.8.10 csp-s 模拟赛18
代码随想录Day11
150. 逆波兰表达式求值 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: 有效的算符为 &#39;+&#39;、&#39;-&#39;、&#39;*&#39; 和 &#39;/&#39; 。 每个操作数(运
代码随想录Day11 代码随想录Day11
使用 extract_sqlaudit_proc 存过分析ob性能问题
最近在某个金融单位核心系统项目做ob的性能压测,期间遇到不少问题,现场两周了每天都加班到凌晨一两点左右,真的是累死。&#129314;&#129314; 我其实进ob之前有心理预期,卷就卷吧,八九点下班也能接受,没想到真到了干项目的情况下,天天凌晨下班,真怕不知道啥时候会猝死。&#128514;&#
使用 extract_sqlaudit_proc 存过分析ob性能问题 使用 extract_sqlaudit_proc 存过分析ob性能问题 使用 extract_sqlaudit_proc 存过分析ob性能问题
[rCore学习笔记 024]多道程序与协作式调度
写在前面 本随笔是非常菜的菜鸡写的。如有问题请及时提出。 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 本节重点 主要是对&#160;任务&#160;的概念进行进一步扩展和延伸:形成 任务运行状态:任务从开始到
[rCore学习笔记 024]多道程序与协作式调度 [rCore学习笔记 024]多道程序与协作式调度 [rCore学习笔记 024]多道程序与协作式调度
Turbo Sparse:关于LLM稀疏性的探索
本文地址:https://www.cnblogs.com/wanger-sjtu/p/18352898 关于llama稀疏性的观察 llama原始模型的FFN计算过程为: \[f(x) = \text{silu}(xW_{Gate}) \odot xW_{UP} \times W_{Down} \]
Turbo Sparse:关于LLM稀疏性的探索 Turbo Sparse:关于LLM稀疏性的探索 Turbo Sparse:关于LLM稀疏性的探索
散知识点总结(持更)
有一些小 trick,专门用一整篇博客来写不太合适,所以都放在这里吧。 逆序对 考试的时候树状数组做法显然比其他的都好写。 考虑每个元素对答案的贡献,我们需要知道在它之前有多少元素比它大。 我们只需要维护一个权值树状数组,在枚举到 \(i\) 的时候查询当前树状数组中的元素有多少比它大,为了方便处理
全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性
在 Python 中,文档字符串(Docstring)是一种用于为模块、类、方法或函数编写文档的字符串,通常放置在定义的开头,紧跟在声明之后。文档字符串使用三重引号(''' 或 ''')包围,可以跨越多行。
全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性 全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性 全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性
上云避坑指南
我在之前的文章《云计算-虚拟化-OpenStack》里聊过,云计算的本质是一种IT资源通过虚拟化进行的共享,是一种更高维度的服务。云计算的本质就俩词:共享、服务。 1、建议企业上云 作为一个IT行业14余年的老杆子,亲自主导过几个公司的中大型系统从IDC机房迁移上云。上云这个事情,是必然趋势,而且我
上云避坑指南 上云避坑指南 上云避坑指南