明明有更好的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 这些黑魔法无法在源生成器中使用

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

  1. selenium2 Webdriver + Java 自动化测试实战和完全教程

    selenium2 Webdriver + Java 自动化测试实战和完全教程一.快速开始 博客分类: Selenium-webdriverselenium webdriver 学习selenium ...

  2. @Value竟然能玩出这么多花样

    前言 对于从事java开发工作的小伙伴来说,spring框架肯定再熟悉不过了.spring给开发者提供了非常丰富的api,满足我们日常的工作需求. 如果想要创建bean实例,可以使用@Controll ...

  3. 一个朋友js图表开发遇到的问题 解决思路c和js

    引言            不求知道一切, 只求发现一件 -- 乔治·西蒙·欧姆 附注:那些存在于梦幻中的事迹,那些儿时梦中的人物,每每看起,都觉得 .哎 .... 岁月 ... 一直在努力 ...  ...

  4. 一个朋友 js图表开发问题 用 c和 js 解决

    引言            不求知道一切, 只求发现一件 -- 乔治·西蒙·欧姆 附注:那些存在于梦幻中的事迹,那些儿时梦中的人物,每每看起,都觉得 .哎 .... 岁月 ... 一直在努力 ...  ...

  5. 吐槽:Lambda表达式

    前面我曾经讨论过Lambda表达式(也就是匿名表达式)的用法, 这里我就主要强调一下匿名表达式的好处. 首先是不需要写多余的方法体,特别是订阅事件的时候,但是也有一个问题,那就是单个方法会因为匿名表达 ...

  6. ETL开发面试问题加吐槽加职业发展建议

    写在前面: 作为甲方,对于乙方派来的开发人员,我是会自己面一下.总体来说遇到的水平不一,于是经过这三年多的面(cui)试(can),总结了一套自己的面试套路,中间也遇到过很多想吐槽的东西,于是大概记录 ...

  7. [BZOJ]4644: 经典傻逼题

    某天我觉得一切题目都是那么不可做,于是百度了一下"傻逼题"-- 题目大意:对于图中的任意一个点集(可以为空或者全集),所有恰好有一个端点在这个点集中的边组成的集合被称为割.一个割的 ...

  8. 由一篇吐槽对String空字符串判断的文章所引发的碎碎念

    一.起因 最近有篇关于String空字符串判断的文章火了,老是看到这篇文章,既然如此我也只好认真看了下:程序员晒出一段代码引来无数网友狂喷!网友:你就活该当码农! 我也觉得这段代码写的不怎么的,首先程 ...

  9. 悲催的IE6 七宗罪大吐槽(带解决方法)第一部分

    一.奇数宽高 悲剧的IE6啊,为何有如此多bug,但用户市场又那么大,真让我们搞网站的纠结.今天就遇到了一个非常奇怪但又很细节的一个bug,一个外部的相对定位div,内部一个绝对定位的div(righ ...

  10. jQuery对象和DOM对象使用说明,需要的朋友可以参考下。

    jQuery对象和DOM对象使用说明,需要的朋友可以参考下.1.jQuery对象和DOM对象第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DOM对象,因此需要重点了解jQuery ...

随机推荐

  1. @Async异步方法对异常的处理,从内层向外层抛出机制

    @Async异步方法对异常的处理,从内层向外层抛出机制 @RequestMapping(value = "/test", method = RequestMethod.GET) p ...

  2. [flask]统一API响应格式

    前言 在设计API返回内容时,通常需要与前端约定好API返回响应体内容的格式.这样方便前端进行数据反序列化时相应的解析处理,也方便其它服务调用.不同公司有不同的响应内容规范要求,这里以常见的JSON响 ...

  3. UIButton选择状态下长按时会变回原始状态

    问题大概就像这样 (请无视那红字) 一般而言这是高亮状态的设置有所缺乏.完善代码如下: [_pupilBtn setImage:[UIImage imageNamed:@"a1"] ...

  4. 用Tensorboard在VScode Remote ssh中显示图像

    可以用Tensorboard在vscode的ssh连接中显示plot出的图像. 具体方法如下: from torch.utils.tensorboard import SummaryWriter wr ...

  5. hbase第一课:hbase-2.2.7分布式搭建

    hbase-2.2.7分布式搭建文档 1.上传解压配置环境变量 # 1.解压 tar -xvf hbase-2.2.7-bin.tar.gz.gz # 2.配置环境变量 vim /etc/profil ...

  6. 解决:编译安卓源码时 JDK 报错 error='Not enough space' (errno=12)

    背景 在编译 Android 10 代码的时候,OpenJDK发现报错: OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(.., . ...

  7. 【动画进阶】类 ChatGpt 多行文本打字效果

    今天我们来学习一个有意思的多行文本输入打字效果,像是这样: 这个效果其实本身并非特别困难,实现的方式也很多,在本文中,我们更多的会聚焦于整个多行打字效果最后的动态光标的实现. 也就是如何在文本不断变长 ...

  8. tcp_tw_reuse、tcp_tw_recycle、tcp_fin_timeout参数介绍

    参数介绍 net.ipv4.tcp_tw_reuse = 1 表示开启重用.允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭: net.ipv4.tcp_tw_rec ...

  9. P5327 题解

    考虑把贡献摊到每个点上计算,每个点带来的贡献实际上是经过它的路径并大小,算完求和之后在除以 \(2\) 就得到了答案. 考虑怎么计算路径并大小. 考虑这样一个办法,将所有路径的起始点和终点按照 DFS ...

  10. 机器学习策略篇:详解如何使用来自不同分布的数据,进行训练和测试(Training and testing on different distributions)

    如何使用来自不同分布的数据,进行训练和测试 深度学习算法对训练数据的胃口很大,当收集到足够多带标签的数据构成训练集时,算法效果最好,这导致很多团队用尽一切办法收集数据,然后把它们堆到训练集里,让训练的 ...