@Author : Lewis Tian (taseikyo@gmail.com)
@Link : github.com/taseikyo
@Range : 2021-09-12 - 2021-09-18
Weekly #46
readme | previous | next
本文总字数 2439 个,阅读时长约:4 分 45 秒,统计数据来自:算筹字数统计 。
*Photo by ann vahurova on Unsplash
Table of Contents
review
为什么大多数软件工程师更喜欢 mac(Medium :-1:)
用 Golang 在秒级读取 16GB 的文件(Medium)
tip
MDvideo: Markdown To Video
计算一个数的绝对值的问题是完全微不足道的。如果数字是负的,改变符号。否则,就让它保持原样。
在 Java 中,它可能看起来像这样:
复制 public static double abs( double value) {
if (value < 0 ) {
return - value;
}
return value;
}
在 IEEE-754 标准中,特别是在 Java 中,有两个零: +0.0
和 -0.0
,把 1.0 除以 +0.0
和 -0.0
,你会得到完全不同的答案: +Infinity
和 ,在比较操作中,+0.0
和 -0.0
是无法区分的。因此,上面的实现 -0.0
的转正:
复制 double x = - 0.0 ;
if ( 1 / abs(x) < 0 ) {
System . out . println ( "oops" );
}
直接改为 if (value < 0 | | value =-0.0)
是不行的,因为 +0.0 = -0.0
为了可靠地区分他们俩,有一个 double.compare
方法:
复制 public static double abs( double value) {
if (value < 0 || Double . compare (value , - 0.0 ) == 0 ) {
return - value;
}
return value;
}
上述方法虽然有效,但是对于这样一个简单的操作变得非常慢
实现 double.compare
并不是那么简单。对于正数需要两次额外的比较,对于 -0.0
需要三次比较,对于 +0.0
需要多达四次比较。
如果看下 double.compare
的源码 ,可以看到实际上我们仅需要一个 doubleToLongBits
,此方法将 double
类型的二进制表示重新解释为 long
类型:
复制 private static final long MINUS_ZERO_LONG_BITS =
Double . doubleToLongBits ( - 0.0 );
public static double abs( double value) {
if (value < 0 ||
Double . doubleToLongBits (value) == MINUS_ZERO_LONG_BITS) {
return - value;
}
return value;
}
doubleToLongBits
将 NaN 规范化(canonicalizes)了,doubleToLongBits
将 NaN 转化为 0x7ff8000000000l
,很显然又多了条件判断。
还有另一种方法 doubleToRawLongBits
。它不执行任何智能的 NaN 转换,只是返回完全相同的位表示:
复制 private static final long MINUS_ZERO_LONG_BITS =
Double . doubleToRawLongBits ( - 0.0 );
public static double abs( double value) {
if (value < 0 ||
Double . doubleToRawLongBits (value) == MINUS_ZERO_LONG_BITS) {
return - value;
}
return value;
}
JIT 编译器可以完全删除 doubleToRawLongBits 方法调用,因为这只是重新解释 CPU 寄存器中存储的比特集的问题,这样 Java 数据类型就会一致。
但是比特本身保持不变,CPU 通常不关心数据类型。虽然有传言说,这个电话仍然可能导致从一个浮点寄存器转移到一个通用寄存器。尽管如此,它还是非常快。
我们能做得更少吗?
事实证明,我们可以从 0.0 中减去 0,从而把正的和负的零都变成正的:
复制 System . out . println ( 0.0 - ( - 0.0 )); // 0.0
System . out . println ( 0.0 - ( + 0.0 )); // 0.0
因此,我们可以用以下方法重写实现:
复制 public static double abs( double value) {
if (value <= 0 ) {
return 0.0 - value;
}
return value;
}
如果我们观察 IEEE-754 格式的 双精度的二进制表示 ,我们可以看到符号只是一个最高有效位。因此,我们只需要无条件地清除这个 bit。
在此操作期间,数字的其余部分不会更改。在这方面,小数甚至比整数更简单,负数通过二的补数变为正数。当然,我们还需要重新解释一个双重数字,并且在操作之后重新解释它:
复制 public static double abs( double value) {
return Double . longBitsToDouble (
Double . doubleToRawLongBits (value) & 0x7fffffffffffffffL );
}
垃圾文章也要会员就离谱。
作者说了下为什么开发者更喜欢 Mac 的 5 个理由:
1、苹果的生态
2、苹果的操作系统
3、可靠性
这一点真没体会到,已经收到两封安全部门的邮件通知赶紧升级,有高危漏洞。。
4、设计
设一点确实,UI 和交互确实吊打 Windows
5、独占软件
这一点应该骂苹果才对
当今世界的任何计算机系统每天都产生大量的日志或数据。随着系统的发展,将调试数据存储到数据库中是不可行的,因为它们是不可变的,而且只能用于分析和故障解决目的。因此,组织倾向于将其存储在驻留在本地磁盘存储器中的文件中。
首先用标准的 Go 的 os.File
来处理文件 IO:
复制 f, err := os. Open (fileName)
if err != nil {
fmt. Println ( "cannot able to read the file" , err)
return
}
// UPDATE: close after checking error
defer file. Close () //Do not forget to close the file
一旦打开文件,有两个选项继续:
1、逐行读取文件,这有助于减少对内存的压力,但在 i/o 中需要更多的时间
2、立即将整个文件读入内存并处理该文件,这将消耗更多内存,且会显著增加时间
由于文件太大,不可能将整个文件加载到内存中
但是第一种选择也是不可行的,因为想在秒级处理文件
还有第三种选择:使用 bufio.NewReader()
分块加载文件:
复制 r := bufio. NewReader (f)
for {
buf := make ([] byte , 4 * 1024 ) //the chunk size
n, err := r. Read (buf) //loading chunk into buffer
buf = buf[:n]
if n == 0 {
if err != nil {
fmt. Println (err)
break
}
if err == io.EOF {
break
}
return err
}
}
当读取了一块,可以起一个线程来处理,这样就可以同时处理每个块:
复制 //sync pools to reuse the memory and decrease the preassure on //Garbage Collector
linesPool := sync . Pool {New: func () interface {} {
lines := make ([] byte , 500 * 1024 )
return lines
}}
stringPool := sync . Pool {New: func () interface {} {
lines := ""
return lines
}}
slicePool := sync . Pool {New: func () interface {} {
lines := make ([] string , 100 )
return lines
}}
r := bufio. NewReader (f)
var wg sync . WaitGroup //wait group to keep track off all threads
for {
buf := linesPool. Get ().([] byte )
n, err := r. Read (buf)
buf = buf[:n]
if n == 0 {
if err != nil {
fmt. Println (err)
break
}
if err == io.EOF {
break
}
return err
}
nextUntillNewline, err := r. ReadBytes ( '\n' ) //read entire line
if err != io.EOF {
buf = append (buf, nextUntillNewline ... )
}
wg. Add ( 1 )
go func () {
//process each chunk concurrently
//start -> log start time, end -> log end time
ProcessChunk (buf, & linesPool, & stringPool, & slicePool, start, end)
wg. Done ()
}()
}
wg. Wait ()
}
利用了多线程(goroutine)之后,处理时间为 25 秒。
实际上作者可以试试单线程,然后对比一下时间改进(
代码:process_16gb_file.go
一个将 markdown 文档转为视频的便捷工具
1. 战战兢兢,如临深渊,如履薄冰
曾子有疾,召门弟子曰:“启予足!启予手!诗云:‘战战兢兢,如临深渊,如履薄冰。’而今而后,吾知免夫,小子!”
曾子病危,把学生们召集起来说:“把我的脚露出来,把我的手露出来。《诗经》上说:‘要小心谨慎做事啊,就像在深水潭边一样,就像在薄冰上行走一样。’从今往后,我知道我不会犯错了,学生们。”
生而在世,既不是嬉戏玩乐,也就不能轻易斫(zhuo)杀、轻易取舍,当是如临深渊,如履薄冰。
readme | previous | next