测试环境: win10 ltsc 2021, .net6, java8
基于.net 没啥好用的日志组件, 就自己写了.
因为每条日志都要记录一个时间, 结果发现 C#的 DateTime.Now 是真的慢, 跟 java 的 new Date()对比了下,至少是几十倍的的性能差距.
调用 1 亿次 DateTime.Now 要好几秒, 而调用 1 亿次 new Date()只要几百毫秒.
而且我还是在 windows 上测试的, java 日期在 windows 上本身就比 linux 慢一两个数量级, 这要是放到 linux 下测试, .net core 日期操作岂不是要比 java 慢上几百倍?
java 还可以通过 System.currentMillis 直接获取当前时间戳, 省掉一大堆无必要的操作. .net core 我找了很久, 貌似没有这个东西.
就一个很简单很简单的测试, 懒得放代码, 果然被攻击了, 那就给一下代码吧.
另外楼下提到的DateTimeOffset.Now.ToUnixTimeMilliseconds(), 请注意一下, .net日期类里的Now和UtcNow不是静态字段, 而是C#特有的属性, 类似java里的方法, 看似没带小括号, 实际是对get方法的调用. DateTime.Now实际是要new一个DateTime的.
我个人觉得对当前时间戳的调用, 应该会是一个相当频繁的操作, 不仅仅是日志里用的多, 所以对于时间戳性能的要求, 是再怎么过份都不为过的. 有时就是想拿一下时间戳, 甚至都不需要进一步格式化, 这个时候java里能通过System.currentTimeMillis()直接拿到, 而.net里对应的方法是DateTime类里面的private static unsafe readonly delegate* unmanaged[SuppressGCTransition]<ulong*, void> s_pfnGetSystemTimeAsFileTime
, 外部程序是没法调用的, 只能捏着鼻子先new一个DateTime再去拿时间
还有说.net日志库好用的很, 我自己写是多此一举. 这个绝大多数情况下都是对的. 但是新发的nlog5.0改了多少默认设置(https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html), 真有那么好用, 还做这么大的改动干嘛? serilog这个我使用过程中, 丢过一次日志, 而且屁大点功能几十行代码也要分个模块, 项目build后serilog相关的dll占了所有dll的一半还有多, 大有向node_modules看齐的趋势
using System.Diagnostics;
// warm up
for (var i = 0; i < 100_000_000; i++)
{
var now = DateTime.Now;
}
// start test
var watch = Stopwatch.StartNew();
for (var i = 0; i < 100_000_000; i++)
{
var now = DateTime.Now;
}
Console.WriteLine(watch.ElapsedMilliseconds);
耗时 3818 毫秒
import java.util.Date;
public class Main {
public static void main(String[] args) {
// warm up
for (var i = 0; i < 100_000_000; i++)
{
var now = new Date();
}
// start test
long start = System.currentTimeMillis();
for (var i = 0; i < 100_000_000; i++)
{
var now = new Date();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
耗时 364 毫秒
1
u823tg 2022-09-03 12:27:23 +08:00
上代码啊,可以帮忙跑下。
|
2
userforg2021 2022-09-03 12:40:34 +08:00
大佬不先点评一下 Serilog 、NLog 、Microsoft.Extensions.Logging 、log4net 这些怎么不好用?
|
3
thinkershare 2022-09-03 12:42:49 +08:00 4
不知道你在说啥, 你这样提问, 不提供代码, 没有任何意义. .NET 日志库一大堆, 我完全不相信你自己写的日志组件能达到第三方日志组件的水平, 因为你一看对.NET 常规 API 都不熟练.
|
5
Leviathann 2022-09-03 13:01:59 +08:00 via iPhone
为什么要用一个被官方标记 deprecated 的类
我说的是 date |
6
thinkershare 2022-09-03 13:21:04 +08:00 1
https://imgur.com/QUQOi6q.jpg ,Java 提供的 Date 类型早就被废弃, 和 DateTime 提供的 API 都没法公平比较. 根本就不应该被使用. .NET 提供的 DateTime 其实也应该被废弃, 使用 DateTimeOffset 替代.
|
7
Bingchunmoli 2022-09-03 13:29:47 +08:00 via Android
@thinkershare 实际是项目依旧 1.8 ,有些序列化支持 D'ateTime 还需要额外操作,date 实际代码用的还是更多。 还是早点更替把,框架支持跟上
|
8
nightwitch 2022-09-03 14:01:19 +08:00
@thinkershare 字体不错,求分享
|
9
Maboroshii 2022-09-03 14:07:15 +08:00 via Android
.net 性能确实不咋地… 但是最近也需要用.net 开发。
搭车请教一下.net6 怎么用 github 上别人写的库?我拉下来发现.net 版本不一致都不能跑… 是一个一个文件复制到我自己的项目里面用吗 |
10
a33291 2022-09-03 14:08:45 +08:00 4
系统 win11 22h2 22622.590
cpu i5-9600K 内存 32G 以下代码均不开优化,否则可能会被优化掉 场景 1 格式化为字符串 C# netcore 6.0.400 ``` using System; using System.Diagnostics; var sw = Stopwatch.StartNew(); for (var i = 0; i < 1_000_000; i++) { var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //var now = DateTime.Now; } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); ``` Java 1.8.0.333 ``` package com.company; import java.util.Date; import java.text.SimpleDateFormat; class Playground { public static void main(String[] args) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); long stime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Date now = new Date(); String nowString = format.format(now); } long etime = System.currentTimeMillis(); System.out.printf("%d ", (etime - stime)); } } ``` 以上代码分别执行 5 次 C# 243 232 237 233 236 Java 617 629 619 632 616 场景 1 只读取当前时间,不做格式化 C# netcore 6.0.400 ``` using System; using System.Diagnostics; var sw = Stopwatch.StartNew(); for (var i = 0; i < 1_000_000; i++) { //var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var now = DateTime.Now; } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); ``` Java 1.8.0.333 ``` package com.company; import java.util.Date; import java.text.SimpleDateFormat; class Playground { public static void main(String[] args) { //SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); long stime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Date now = new Date(); //String nowString = format.format(now); } long etime = System.currentTimeMillis(); System.out.printf("%d ", (etime - stime)); } } ``` 以上代码分别执行 5 次 C# 39 38 39 39 39 Java 9 10 10 9 9 结论 1. 均执行时间字符串格式化时,C#比 Java 快 2. 仅执行获取当前时间的代码时,java 更快 关于结论 2 的分析 通过查看源代码,java 的 new Date() 仅执行一个成员赋值 ``` public Date() { this(System.currentTimeMillis()); } public Date(long date) { fastTime = date; } private transient long fastTime; ``` 而 C#的 Date.Now 源码 ``` public static DateTime Now { get { DateTime utc = UtcNow; long offset = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utc, out bool isAmbiguousLocalDst).Ticks; long tick = utc.Ticks + offset; if ((ulong)tick <= MaxTicks) { if (!isAmbiguousLocalDst) { return new DateTime((ulong)tick | KindLocal); } return new DateTime((ulong)tick | KindLocalAmbiguousDst); } return new DateTime(tick < 0 ? KindLocal : MaxTicks | KindLocal); } } public static unsafe DateTime UtcNow { get { ulong fileTimeTmp; // mark only the temp local as address-taken s_pfnGetSystemTimeAsFileTime(&fileTimeTmp); ulong fileTime = fileTimeTmp; if (s_systemSupportsLeapSeconds) { // Query the leap second cache first, which avoids expensive calls to GetFileTimeAsSystemTime. LeapSecondCache cacheValue = s_leapSecondCache; ulong ticksSinceStartOfCacheValidityWindow = fileTime - cacheValue.OSFileTimeTicksAtStartOfValidityWindow; if (ticksSinceStartOfCacheValidityWindow < LeapSecondCache.ValidityPeriodInTicks) { return new DateTime(dateData: cacheValue.DotnetDateDataAtStartOfValidityWindow + ticksSinceStartOfCacheValidityWindow); } return UpdateLeapSecondCacheAndReturnUtcNow(); // couldn't use the cache, go down the slow path } else { return new DateTime(dateData: fileTime + (FileTimeOffset | KindUtc)); } } } ``` 可以看出 java 的实现在仅 new Date()的情况下,执行的操作比 C#少很多,诸如 getTime 等都是调用时才计算,而 C#这边则是已经需要计算很多成员所以更慢. |
11
thinkershare 2022-09-03 14:23:53 +08:00
@Maboroshii .NET 6.0/7.0 在所有带 runtime 的机器中,包括 go, 我想不到 web 这块, 有哪门平台比.NET 高. 你可以去专业的 Benchmark 平台自己看看. 性能更好的前面都是些 rust/c/c++这种, go 在 web 项目上也没有性能优势.
另外.NET 的标准库是 NuGet 包, nuget.org, dotnet sdk/vscode/visual studio 的提供了安装依赖的方法. 绝大部分 github 都会提供 NuGet 包给最终用户使用. |
12
thinkershare 2022-09-03 14:25:34 +08:00
@nightwitch 我用的是一个开源字体 Iosevka, 主要是为了它的连字符功能, 这个字体其实很丑, 好在它是严格等宽字体, 中文是严格的 2 被英文字体间距.
|
13
Maboroshii 2022-09-03 15:57:26 +08:00 via Android
@thinkershare 性能问题我才写了测试,循环计算圆形面积 2000w 次,睡眠 50ms ,c#的 cpu 占用比 go 高很多
|
14
thinkershare 2022-09-03 16:17:34 +08:00 2
@Maboroshii 找个专业的完整性能评测网站去看看, 专业的性能测试是怎么写的. 性能测试是个非常麻烦的事情, 不是你简单的跑个 demo 就能搞清楚的. 有各种详细的指标. .NET 6 有完整的堆栈控制能力, C#提供了完整的指针控制能力, 大量的为性能而设计的库, 如果都在编译为机器的原生指令和后(JIT 完成后), 除了库本身实现的性能差异, 我想不到你使用 go 写的代码性能会比 C#高非常, 即便使用 C 写, 也不会高出太多, 另外, 说到性能测试, 至少需要像我上面那样将代码贴出来. 在你对两门语言掌握的熟练度相差很大的时候, 你写出来的代码中的某一种很可能不是最优解, 很大可能是你的写法导致了性能巨大的差异.
|
15
a33291 2022-09-03 16:19:27 +08:00 2
|
16
jiangzm 2022-09-03 16:29:37 +08:00
这标题太唬人
|
17
PendingOni 2022-09-03 17:12:22 +08:00
@a33291 想到我之前线上的一个写的一个 Bug,List 改成 IQueryable 之后查询慢了不是一星半点儿 orz...
|
18
leeg810312 2022-09-03 17:29:31 +08:00 via Android
.net 日志组件自己写?这么多日志组件没有你造的轮子好?
|
19
PendingOni 2022-09-03 17:42:25 +08:00
Java 的 System.currentMillis()
.Net 上有 DateTimeOffset.ToUnixTimeSeconds() https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimeseconds?view=net-6.0 |
20
reallittoma 2022-09-03 18:26:57 +08:00
@userforg2021 #2 看不上这些库
|
21
mmdsun 2022-09-03 19:18:22 +08:00 1
非.net 开发,帮楼主谷歌了一下:long time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
.net core 性能应该比 Java 好,常年测评都是接着者 C/C++、rust 后面 |
22
Aloento 2022-09-03 19:34:50 +08:00
这个问题看的我两眼一黑
今日笑话( 1/1 ) |
23
ysc3839 2022-09-03 20:37:11 +08:00 via Android
有一说一 Java 计算现实时间差应该用 System.nanoTime()。System.currentTimeMillis()的精度不一定高。
|
24
Maboroshii 2022-09-03 23:46:02 +08:00
@a33291
@thinkershare 麻烦指导下, 分别都是用 dotnet run , go run 直接跑的,cpu 占用差了 1 倍。 ```c# class Entry { public static void Main() { const int CAL_COUNT = 20000000; while (true) { for (var i = 0; i < CAL_COUNT; i++) { double pi = 3.14; double r = 123.456; _ = pi * r * r; } Thread.Sleep(1000 / 20); } } } ``` ```go package main import ( "time" ) const ( CAL_COUNT = 20000000 ) func main() { for { for i := 0; i < CAL_COUNT; i++ { r := 123.456 pi := 3.14 _ = pi * r * r } time.Sleep(time.Millisecond * 1000 / 20) } } ``` |
25
yazinnnn 2022-09-04 08:18:18 +08:00 3
就是因为你这种人,java boy 才会被误解.jpg
|
26
userforg2021 2022-09-04 09:11:52 +08:00
@Maboroshii dotnet run -c Release
|
27
a33291 2022-09-04 09:51:14 +08:00 1
@Maboroshii
是的,请分别在 release 模式下编译后,单独运行进行比较 C# netcore 6.0.400 ``` dotnet build -c Release ./main.exe ``` cpu 占用 最高 2.95% 最低 1.53% 平均 2.2% 内存 15.71MB go 1.19 ``` go build -ldflags "-s -w" main.go ./main.exe ``` cpu 占用 最高 3.39% 最低 1.54% 平均 2.5% 内存 15.75MB 以上数据均运行多次,取最优 就仅针对测试代码来说,这些数据表明 c#和 go 性能基本一致.但是要注意, 1. 这里边其实包含了编译器本身的 buff 加持,即编译器本身的优化也会影响结果,因为测试代码相对简单,极有可能会被编译器优化掉(暂未对比汇编). 2. go 中 routine 无处不在,通过查看 2 个程序的线程 cycles delta 等参数可知,go 在运行时多个线程均较高,C#版本仅主线程较高.也就是可能他们的调度机制不一样. 另外说明,我对 go 不了解,对 go 的信息了解不一定正确(如有误请指正). 结论 单就以上测试代码来说,我觉得他们是性能持平的(实际场景业务复杂,不好拉平衡量,暂不讨论). 附 C#的 csproj 文件 ``` <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <OutputPath>.</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> </PropertyGroup> </Project> ``` |
28
sunhelter 2022-09-04 10:40:53 +08:00
@Maboroshii GitHub 的库一般都会附带 NuGet 链接,在项目里引入 NuGet 包直接调用就行了
|
29
iold 2022-09-04 11:03:31 +08:00
c#,获取当前时间戳
DateTimeOffset.Now.ToUnixTimeMilliseconds(); DateTimeOffset.Now.ToUnixTimeSeconds(); DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); DateTimeOffset.UtcNow.ToUnixTimeSeconds(); |
31
a33291 2022-09-04 11:27:39 +08:00
@beyondex 你好,是 @Maboroshii 这位朋友先说测试代码存在 cpu 占用差异,所以我这里才比较的 cpu.
个人看法哈,对于外部因素都基本一样(代码逻辑一致 硬件环境一致)的场景下,可以横向比较一下. |
32
bdnet 2022-09-04 11:28:10 +08:00
不得不说标题取得好…(啥玩意啊
|
33
hez2010 2022-09-04 11:47:34 +08:00 via iPad
@Maboroshii dotnet run 直接跑是 Debug ,需要加 -c Release ,而 go 默认是 Release 。你在用 .NET 的 Debug 跟 go 的 Release 做比较,当然显得 go 跑得更快占用更低。
|
34
Maboroshii 2022-09-04 13:45:35 +08:00 via Android
|
35
thinkershare 2022-09-04 14:31:47 +08:00
@Maboroshii 就是带了 Sleep 也不能用它来比较程序的耗费时间, .NET 的 Thread.Sleep 从诞生开始就是个错误, 这个 API 一开始就应该被移除. 性能测试至少也需要使用 Stopwatch, 至少要学会控制变量. 你使用了 Sleep 后, 完全没法控制变量. Sleep 在.NET 中是一个精确度非常差的 API.
|
36
clorischan 2022-09-04 15:27:20 +08:00 1
@Maboroshii 循环的耗时就没保证一致, 完全没有参考价值
就是不算考虑 Sleep 本身的问题, 就当 Sleep 绝对精确好了 例如: 程序 A: 循环耗时 150ms, Sleep 50ms, 每轮共计耗时 200ms, CPU 占用 1% 程序 B: 循环耗时 50ms,Sleep 50ms, 每轮共计耗时 100ms, CPU 占用 2% 相同时间内 B 计算次数跟 CPU 占用都翻倍了, 那么你认为那个好 |
37
thinkershare 2022-09-04 15:54:30 +08:00 1
@clorischan 他缺乏对编程语言和库性能评测的基本常识, 看他给出来的代码就知道了, 这样的问题回答毫无意义.
我现在明白了, 这是一个钓鱼问题, V2EX 时不时就冒出这种奇奇怪怪的, 纯粹为了骗铜币而提出的问题. 一群人(包括我)傻傻的在下面回答. V2EX 上问题的质量人眼可见下降, 现在居然有很多人开始在上面卖水果了... |
38
ragnaroks 2022-09-05 12:21:49 +08:00
我真不行 dotnet 在有 MSDN docs 的加持下会有这么高的上手门槛
|
39
leegradyllljjjj 2022-09-06 13:14:54 +08:00
扎蛙人都被扎蛙搞魔怔了,事实上市场里比 java 先进的语言有很多
|
40
bthulu OP @userforg2021 NLOG 好不好用, 你看看 NLOG 新发的 5.0 版本( https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html)的优化改进不就知道了, 修改了那么多的特性, 改了多少默认设置, 足以证明之前版本的 NLOG 并不好用.
@PendingOni @iold 这些都是要先 New 一个日期实例, DateTimeOffset.UtcNow 也是 New 了一个实例, 这个实例里做了大量的日期设定类操作, 而我仅仅只需要当前时间戳就行了. 上面有位兄弟也给出了代码对比了, 至少 4 倍差距. 我自己本地联想小新电脑上测试性能差距更大一些, DateTime.Now 和 new Date()各跑一亿次, .net 耗时 3818 毫秒, java 耗时 364 毫秒, 相差 10 倍. ------------------------------------------------------ --------------.net6.0.8 测试 ------------------------------------------------------ using System.Diagnostics; // 热身 for (var i = 0; i < 100_000_000; i++) { var now = DateTime.Now; } // 开跑 var watch = Stopwatch.StartNew(); for (var i = 0; i < 100_000_000; i++) { var now = DateTime.Now; } Console.WriteLine(watch.ElapsedMilliseconds); 耗时 3818 毫秒 ----------------------------------------------------- --------------ms jdk11.0.16.1 (ms1.8 的 jdk 官网找不到了) ----------------------------------------------------- import java.util.Date; public class Main { public static void main(String[] args) { // warm up for (var i = 0; i < 100_000_000; i++) { var now = new Date(); } // start test long start = System.currentTimeMillis(); for (var i = 0; i < 100_000_000; i++) { var now = new Date(); } long end = System.currentTimeMillis(); System.out.println(end - start); } } 耗时 364 毫秒 -----------------end----------------------------------- |
41
PendingOni 2022-09-06 18:19:24 +08:00
![企业微信截图_16624594047008.png]( https://s2.loli.net/2022/09/06/A5nGKW4QZXg9e1j.png)
差不多是的,用时秒级,源码上也是不停的 New 出来的 |
42
bthulu OP @PendingOni 你再试试.Now, 比.UtcNow 还要慢一倍以上
|
43
userforg2021 2022-09-07 17:17:20 +08:00
既然你都看到源码了,那就看下 Java 为什么快吧。
从 jdk11 中可以看到 Windows 获取时间使用的函数是 GetSystemTimeAsFileTime ; https://github.com/openjdk/jdk/blob/jdk-11+28/src/hotspot/os/windows/os_windows.cpp#L917 再看.net6 ,可以看到.net 在系统支持时会使用更精确的时间函数 GetSystemTimePreciseAsFileTime ; https://github.com/dotnet/runtime/blob/v6.0.8/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs#L134 GetSystemTimePreciseAsFileTime 函数在 win8 以上受支持: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime#requirements 这是一个慢的点。但是.net 的 DateTime 精度更高。另外.net 还会计算闰秒: https://github.com/dotnet/runtime/blob/v6.0.8/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs#L23 这是第二个慢的点。也是为了精度考虑的。当然你可以说是.net 的“过度设计”,你不需要精确时间。。。那这个确实就需要自己动手了: public unsafe static class SlimDateTime { private const long TicksPerMillisecond = 10000; private const long TicksPerSecond = TicksPerMillisecond * 1000; private const long TicksPerMinute = TicksPerSecond * 60; private const long TicksPerHour = TicksPerMinute * 60; private const long TicksPerDay = TicksPerHour * 24; private const int DaysTo1601 = 584388; private const int DaysTo1970 = 719162; private const long FileTimeOffset = DaysTo1601 * TicksPerDay; private const ulong KindUtc = 0x4000000000000000; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static DateTime UtcNow() { var ticks = GetTicks(); return *(DateTime*)&ticks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong GetTicks() => OperatingSystem.IsWindows() ? Windows.GetTicks() : (ulong)DateTime.UtcNow.Ticks; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong UtcNowUnixTimeMilliseconds() => OperatingSystem.IsWindows() ? Windows.UtcNowUnixTimeMilliseconds() : (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); private static class Windows { [DllImport("kernel32", EntryPoint = "GetSystemTimeAsFileTime")] [SuppressGCTransition] public extern static void GetSystemTimeAsFileTime(ulong* time); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong GetTicks() { ulong fileTime; GetSystemTimeAsFileTime(&fileTime); return fileTime + (FileTimeOffset | KindUtc); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong UtcNowUnixTimeMilliseconds() { ulong fileTime; GetSystemTimeAsFileTime(&fileTime); return (fileTime - ((DaysTo1970 - DaysTo1601) * TicksPerDay)) / TicksPerMillisecond; } } } 其他的就不评价了,懒得打字; |