1 | package main |
崩溃信息如下:
1 | panic: unaligned 64-bit atomic operation |
在Go源码go/src/sync/atomic/doc.go
的注释中,有如下描述:
1 | // BUG(rsc): On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX. |
大致意思是,在一些32位的环境(包括x86和arm),标准库sync/atomic
中操作int64的函数存在bug,调用者需自行保证,被操作的int64是按64位对齐的。。否则给你来个panic。惊不惊喜意不意外。。
这里简单说一下64位对齐是啥意思,拿上面那个demo来说:
1 | type Foo struct { |
所以,atomic.AddInt64(&f.a, 1)
不会崩溃,atomic.AddInt64(&f.c, 1)
会崩溃。
值得一提的有几点:
sync/atomic
中的函数解决方法有两种:
一种是保证结构体中的需要使用atomic的int64按64位对齐,比如最简单的就是把这些变量的声明写在结构体的最前面。同时,还需要保证这种结构体被其他结构体嵌套时,也要64位对齐。缺点是编写和维护代码的心智负担比较大。
另一种就是把int64变量的原子操作改成mutex互斥锁保护。缺点是mutex比atomic的开销大。
我自己的更佳方案:
在我自己的Go基础库naza(https://github.com/q191201771/naza)中,增加对64位原子整型的封装,让它按运行系统的类型(32/64)做条件编译,64位系统内部使用Go标准库atomic中的原子操作函数实现,32位则内部退化成使用mutex实现。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/21030/
]]>interface io.Reader
定义1 | // go/src/io/io.go |
interface io.Reader
语义从Reader
中读取数据,存入传入参数p
中,并通过返回值n
返回本次读取的大小,以及err
返回可能存在的错误。
对于一次调用:
len(p)
的数据n
取值范围0 <= n <= len(p)
n < len(p)
,也不保证p
内存块后半部分的原始数据保持不变,不被修改n < len(p)
,也会立即返回,而不是等待读够len(p)
大小的数据注意,如果没有数据可读并且也没有错误发生,会阻塞当前协程,直到数据可读或发生错误。
(注意,这里说的阻塞指的是上层代码的表现,Go的IO线程协程调度模型不在本文讨论范围内)
可能同时出现n > 0
并且err != nil
的情况。
当读取到结尾EOF(end-of-file)时,可以有两种方式
n > 0
并且err != nil
n > 0
并且err == nil
第二次调用返回n == 0
并且err == EOF
第一种方式,即使err != nil
,也有可能读取到了数据。
所以,不管是以上哪种方式,正确处理应该是每次调用结束后,先判断n
,处理读取到的数据,再判断err
,像这样:
1 | n, err := r.Read(p) |
n == 0
并且err == nil
n == 0
的情况,除非len(p) == 0
n == 0
并且err == nil
时,应被认为啥事也没发生(一次空调用),而不是EOF函数调用结束后,内部不会持有p
的内存
interface io.Reader
举例:1 | // 1. 读取文件 |
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20120/
]]>本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20101/
]]>本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20100/
]]>本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20080/
]]>包含的内容:
首先大前提,repo原作者拥有一票否决PR的权利。
提PR前,建议先提issue,说说想法。这样PR的成功率比较高。
举两个反例:
以上等情况,可能导致你的工作变为多余无效,所以动手前,先沟通。
user.name
和user.email
必须和发起PR的账号一致【强制!】1 | feat 新功能 |
Hyper Text Transfer Protocol
的缩写,我们写成http或HTTP,不写成Httprequest
的缩写,我们写成req或者Req,不写成REQhttp request
可以写成httpReq,HTTPReq,request http
可以写成reqHTTP,ReqHTTP//
,不使用`//`【强制!】**//
后面加一个空格再写注释内容【强制!】,
而不是,
【非强制】大体分为两类,以#
开头,表示类型,比如#Bug
,以*
开头,表示状态,作为Open和Closed的补充。
一般来说,一个label或PR可以有多个#
类型的label,但是*
类型的label只有一个。
由于github的设计是将Issues和PRs的label放一起管理,所以下面我也会说明label是供Issues还是PRs使用的。
label | Issues | PRs | details |
---|---|---|---|
#Bug | ✔ | ✔ | bug |
#Feature | ✔ | ✔ | 新功能,新特性 |
#Performance | ✔ | ✔ | 性能 |
#Refactor | ✔ | - | 架构、设计方面 |
#Test | ✔ | ✔ | 测试 |
#Question | ✔ | x | 疑问 |
#Need doc | ✔ | - | 需要作者补充文档 |
#Roadmap | ✔ | x | 作者自己提出来的一些待完成项 |
#Duplicate | ✔ | x | 和之前的issue重复 |
*In process | ✔ | - | 处理中。比如正在编写代码实现 |
*Waiting reply | ✔ | ✔ | 等待提出Issues和PRs的人的回复 |
*Answered | ✔ | - | 已给出提问者满意的解答 |
*Solved | ✔ | - | 已解决。比如特性已支持等 |
*PR accepted | x | ✔ | PR已经被合并进主分支中 |
*Invalid PR | x | ✔ | 被拒绝的PR |
*Open another PR | x | ✔ | 建议重提PR,老PR直接关闭 |
*Indefinite delay | ✔ | x | 无限期延迟,比如不确定啥时候会支持的特性 |
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20070/
]]>1 | Encryption Everywhere DV TLS CA - G1 |
我的机器和域名用的都是阿里云。去年申请的时候,送了一年免费的HTTPS证书,现在到期了。
上阿里云看了看证书,都贼贵,掏钱是不可能掏钱的。
网上找个免费的,以下是操作记录。
登录阿里云主机,先安装acme.sh
:
1 | git clone https://github.com/acmesh-official/acme.sh.git |
输出信息如下:
1 | [Sat Jun 6 09:40:07 CST 2020] It is recommended to install socat first. |
上阿里云的控制台获取阿里云API的AccessKey ID和AccessKey Secret。如果没有就创建一个。
地址: https://ak-console.aliyun.com/#/accesskey
弄好后需要关闭终端,重新登录一下阿里云主机,执行如下命令(填入阿里云控制台获取的Ali_Key
,Ali_Secret
。pengrl.com
是我的域名):
1 | export Ali_Key="xxx" |
成功后,会在/root/.acme.sh/pengrl.com
下生成证书相关的文件。
我的博客使用的是nginx接入,直接修改nginx配置文件中和证书相关的配置:
1 | ssl_certificate "/root/.acme.sh/pengrl.com/fullchain.cer"; |
使用如下命令重启nginx:
1 | service nginx restart |
重新在浏览器中访问博客,多打开几个链接看看,再看看图片啥的是否正常。一切OK。
再在chrome中点击那把小锁,看看证书的基础信息:
1 | 签发者:Let's Encrypt Authority X3 |
基本上就完成了。
注意,这里写的过期日期是大概3个月后,我看acme.sh
在crontab中添加了一个以周为单位的定时任务,应该是证书续签用的,我们过段时间再看看过期时间是否会更新。
另外,还可以用这个网站,测试一下。 https://myssl.com/
20200905补充:
blog在0904证书到期了,并没有自动更新。
使用$crontab -e
查看所有定时任务,找到7 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
命令行执行该命令$"/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"
得到结果:
1 | ===Starting cron=== |
使用添加参数--force
,再次执行/root/.acme.sh/acme.sh --force --cron --home "/root/.acme.sh"
成功生成新的证书。
重启nginx,blog访问恢复正常。
之后我更新了crontab任务,时间间隔改为30天,并添加force参数。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20060/
]]>1 | replace github.com/q191201771/naza => /Volumes/Data/chef_git/naza |
其中github.com/q191201771/naza
是依赖的module的github repo url,/Volumes/Data/chef_git/naza
是本地目录。
举个栗子。
我有一个开源的Go流媒体服务器项目lal,依赖另一个提供底层基础通用功能的项目naza。
- github.com/q191201771/lal
- github.com/q191201771/naza
两个项目都是我自己在维护。使用go mod管理代码。
lal的go.mod是这样:
1 | module github.com/q191201771/lal |
开发lal时,有时需要改naza的代码,比如在naza中新增功能或修复bug。
常规操作,是每次修改完naza代码,都提交到github,lal才能看到naza的变化。
如果不想这样,可以使用go mod replace功能,将lal中依赖的naza指向本地目录。做法是在lal的go.mod尾部增加一行replace信息:
1 | replace github.com/q191201771/naza => /Volumes/Data/chef_git/naza |
之后,lal不再依赖的naza的固定版本,而是依赖所指向的本地目录中的naza代码。换句话说,就可以愉快的一边修改naza,一边在lal中看效果,等到naza的本次修改稳定符合预期后,再提交naza到github。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20054/
]]>math/bits
,注意,这个package是Go 1.9
引入的,所以1.9
之前的老版本没法用。做流媒体音视频开发,经常需要做一些位操作,Go标准库math/bits
中提供了一些,本文对它们的功能做个备忘。
1 | func LeadingZeros(x uint) |
上面的func LeadingZeros
会判断uint
是32位还是64位选择LeadingZeros32
或LeadingZeros64
。
因为Go没有泛型,所以不同的数据类型,提供了不同的函数。后文介绍的函数基本都是这样,如果功能上不存在语义上的歧义,就只列一个。
1 | func TrailingZeros(x uint) int |
1 | func OnesCount(x uint) int |
1 | func RotateLeft(x uint, k int) uint |
1 | func Reverse(x uint) uint |
1 | func ReverseBytes16(x uint16) uint16 |
1 | func Len(x uint) int |
我自己也写了一些位操作的实现:package nazabits,比如以流的形式读取写入位等,后续会根据自身的业务需求,添加更多位操作,作为标准库的补充。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20053/
]]>1.14.2
,升级的方法就两步,先上Go官方下载页面( https://golang.org/dl/ )下载macos对应的二进制包(1.14.2
地址 https://dl.google.com/go/go1.14.2.darwin-amd64.tar.gz )
下载好后把压缩包解压到相应的目录:
1 | $sudo tar -C /usr/local -xzf go1.14.2.darwin-amd64.tar.gz |
因为是升级,不是初次安装,之前已经把PATH设置好了,就不用管了。
之后执行以下命令,查看Go版本:
1 | $go version |
看到已经是1.14.2
了,美滋滋。
但是用Go编译自己的程序,输出一大段如下错误:
1 | # runtime/internal/atomic |
错误提示中说,atomic中的很多内容都重定义了,打开目录/usr/local/go/src/runtime/internal/atomic
下的atomic_amd64x.go
和atomic_amd64.go
,发现内容都差不多。
猜想可能新版本的源码文件名变更了,由于我们的新版本是直接解压过去的,导致老版本和新版本的源码文件同时存在。
尝试将/usr/local/go
整个删除,再次sudo tar -C /usr/local -xzf go1.14.2.darwin-amd64.tar.gz
安装新版本。
之后用Go编译程序就没有错误了。
上/usr/local/go/src/runtime/internal/atomic
目录看,只有atomic_amd64.go
文件了,说明猜想正确。问题解决。
总结一下,安装Go时,如果选择安装在老版本目录,最好先把老版本完整删除掉。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20052/
]]>本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20051/
]]>Go标准库中的package log
也支持打印源码文件名和行号,打开方式是设置以下两个标志中的任意一个:
1 | Llongfile // full file name and line number: /a/b/c/d.go:23 |
标准库中所有的日志打印最后都要调用Output
函数,再在里面调用runtime.Caller
获取源码文件名和行号:
1 | // package log |
runtime.Caller
获取源码文件名和行号的方式,是通过查询调用堆栈的信息得到的,这也是为什么调用方需要传入获取栈的层数,也即skip
参数。
而Go中的调用栈,和runtime协程管理栈帧相关。我没有系统学习过这部分内容,所以就不展开分析了,我们直接benchmark数据说话。
先直接对runtime.Caller
做benchmark:
1 | //BenchmarkRuntimeCaller-4 2417739 488 ns/op 216 B/op 2 allocs/op |
单次大概是500纳秒左右的耗时。我们将skip
参数从0增大到2:
1 | //BenchmarkRuntimeCaller2-4 1213971 983 ns/op 216 B/op 2 allocs/op |
可以看到耗时增加到接近1微妙。
我们分别对打印源码文件名,和不打印源码文件名的标准库做benchmark对比:
1 | //BenchmarkLog-4 754929 1672 ns/op 0 B/op 0 allocs/op |
可以看到耗时增加了一倍。benchmark的源码:https://github.com/q191201771/naza/blob/master/playground/p12/p12_test.go
有意思的是,标准库中可能也觉得获取源码文件名的操作太耗时了,所以在调用runtime.Caller
前会先释放锁,等调用结束后,再把锁加回来。这么做锁的粒度是小了点,但是锁的操作变多了。个人觉得还不如把runtime.Caller
的调用移到头次加锁的前面,这样既减少锁粒度,又不增加拿锁的次数。
另外,标准库中,将获取日志时间的time.Now
调用放在了加锁之前,这么做锁的粒度是小了,但是极端情况下,可能先调用time.Now
的协程后获取到锁,也即日志中可能出现后面的日志比前面的日志时间还要早的情况。
另外,标准库中把源码文件名和行号打印在行首,我个人不太喜欢,因为文件名和行号不是定长的,这将导致业务上的日志的起始位置不是固定的,看起来很别扭,我更习惯将文件名和行号打印到行尾。
另外,聊一下c/c++
,它们通过__FILE__, __LINE__, __func__,
这三个宏来获取源码文件名、行号、函数,这些宏会在编译的时候替换为所在源码位置中的文件名等信息。开销比Go要小很多。
最后,我根据自己日常的使用习惯,也写了一个日志库,供参考。github地址:https://github.com/q191201771/naza
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20050/
]]>两点需要注意:
Go官方文档对internal package有相关的描述,粘贴如下:
https://golang.org/doc/go1.4#internalpackages
Go’s package system makes it easy to structure programs into components with clean boundaries, but there are only two forms of access: local (unexported) and global (exported). Sometimes one wishes to have components that are not exported, for instance to avoid acquiring clients of interfaces to code that is part of a public repository but not intended for use outside the program to which it belongs.
The Go language does not have the power to enforce this distinction, but as of Go 1.4 the go command introduces a mechanism to define “internal” packages that may not be imported by packages outside the source subtree in which they reside.
To create such a package, place it in a directory named internal or in a subdirectory of a directory named internal. When the go command sees an import of a package with internal in its path, it verifies that the package doing the import is within the tree rooted at the parent of the internal directory. For example, a package …/a/b/c/internal/d/e/f can be imported only by code in the directory tree rooted at …/a/b/c. It cannot be imported by code in …/a/b/g or in any other repository.
For Go 1.4, the internal package mechanism is enforced for the main Go repository; from 1.5 and onward it will be enforced for any repository.
https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20048/
]]>比如我在我的centos测试服务器上查看nginx依赖的动态库:
1 | $ldd /usr/sbin/nginx |
MacOS没有ldd,可以通过otool -L
命令达到统一的效果,比如:
1 | otool -L /usr/local/go/bin/go |
以及:
1 | otool -L /usr/local/bin/ffmpeg |
参考链接:
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20047/
]]>Hugo主题列表页面貌似不支持排序,它的默认排列顺序可能是基于最近更新时间的。所以写了个小爬x,把主题按github上的点赞数排个序。
以下是原始数据,一行是一个主题,每行4个属性,分别是介绍页,对应的github页,最近更新时间,github star数。
抓取时间是20200418,尾部有几个star数为0的,看了下是因为托管在gitlab上,我就不抓了。
1 | https://themes.gohugo.io/academic/ https://github.com/gcushen/hugo-academic 2020-04-15 4211 |
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20046/
]]>使用如下命令,查看生成core文件的开关是否打开:
1 | ulimit -c |
如上输出0,表示没有打开。如果程序发生段错误崩溃,控制台输出如下:
1 | Segmentation fault |
使用如下命令,打开开关:
1 | $ulimit -c unlimited |
再次查看开关的状态:
1 | $ulimit -c |
程序崩溃后控制台输出如下:
1 | Segmentation fault (core dumped) |
并在硬盘上生成一个core文件。
另外插一嘴,ulimit -c
除了可以设置为ulimited
,实际上还可以设置为数字,用于限制core文件的大小,当core文件超出限制的大小,将被剪裁存储。
但是比较迷的是它使用的单位是block
,1个block是多大,可以参考下《这篇文章》。
按它的意思,要么是512字节,要么是1024字节。
我在一台centos机器测试了一下,设置为10时(ulimit -c 10
),生成的core文件大小为12288,设置为20时,大小为20480。
设置ulimit -c
和export设置环境变量是一样的,只对当前终端有效。可以在每次启动进程前进行设置,或者将设置加入/etc/profile
,~/.bashrc
等初始化配置文件中。
一共有两个设置,使用如下方式查看:
1 | $cat /proc/sys/kernel/core_uses_pid |
如上最简单的配置时,生成的core文件,文件名就是core
,存储路径为执行程序时的路径。
由于文件名是固定的,新生成的core文件会将老的core文件覆盖。
core_users_pid
修改为1时,会在core文件名后添加上pid进程号。比如使用如下方式修改:
1 | $echo '1' > /proc/sys/kernel/core_uses_pid |
生成的core的文件名:core.23715
,其中23715就是崩溃进程的进程号。
通过core_pattern
,可以修改core文件的存储路径,并且还定义了一些标签可以直接使用:
1 | %% a single % character |
我们来测试一下添加存储路径以及程序名,进程号:
1 | mkdir /home/yoko/corepath |
生成的core为:/home/yoko/corepath/core.a.out.25300
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20045/
]]>FFmpeg社区曾经有过一次针对RTMP扩展HEVC的讨论,最终达成的结论是如果Adobe扩展了RTMP,那么FFmpeg才可以接受HEVC扩展。现状是Adobe更新RTMP协议遥遥无期。
为推进HEVC视频编码格式在直播方案中的落地,经过CDN联盟讨论,并和主流云服务厂商达成一致,规范了HEVC在RTMP/FLV中的扩展…
以上内容摘自金山视频云的文章《FFmpeg代码导读系列(一,下半部)—-HEVC在RTMP中的扩展》
金山云在ffmpeg的源码基础上增加了rtmp h265的支持,并且在github上开源。
所谓工欲善其事,必先利其器。第一步,我们先编译代码,得到可以推拉rtmp h265流的ffmpeg,以及可以播放rtmp h265的ffplay。以便后续学习rtmp h265的协议知识。
本来,金山云ffmpeg的git wiki页《H.265 RTMP推流使用指南》上已经有编译的说明,但是我实际操作时遇到了一些问题,所以写了这篇文章。
另外,我之前还写了几篇和ffmpeg编译相关的文章(如果你在编译时遇到问题,也可以看看):
我的实验环境是macOS Catalina 10.15.1
下载ksvc版本的ffmpeg,并切换到对应的3.4版本release分支:
1 | git clone https://github.com/ksvc/FFmpeg.git |
编译:
1 | ./configure --enable-static --enable-pic \ |
报错:
1 | ERROR: x265 not found using pkg-config |
原因是缺少libx265库,我们先下载并编译libx265:
x265的所有版本下载列表: https://bitbucket.org/multicoreware/x265/downloads/
我们选当前的次新版本x265_3.2.1
,开始编译安装:
1 | wget https://bitbucket.org/multicoreware/x265/downloads/x265_3.2.1.tar.gz |
回到ffmpeg目录,按上面的参数继续执行./configure
。
然后编译:
1 | make -j8 |
报错:
1 | libavcodec/libx264.c:282:9: error: use of undeclared identifier 'x264_bit_depth' |
意思是找不到x264_bit_depth
的定义,看名字明显是和x264相关的,打开本地的/usr/local/include/x264_config.h
文件,可以看到:
1 | #define X264_BIT_DEPTH 0 |
说明x264的头文件中是大写的X264_BIT_DEPTH
,所以我就把libavcodec/libx264.c
中的x264_bit_depth
全部替换成了大写的X264_BIT_DEPTH
。
继续make -j8
编译,成功得到ffmpeg和ffplay等可执行文件。
之后,我们就可以尝试推拉h265的流了。
1 | 推流 |
这里需要注意的是,推拉流成功的前提是,你的rtmp服务器支持h265。
我的开源Go流媒体服务器lal已支持rtmp h265,并提供了源码和可执行文件两种形式供大家使用,感兴趣的可以看看,谢谢。
github地址: https://github.com/q191201771/lal
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20044/
]]>本次实验环境:
在ffmpeg目录下执行./configure
(携带好需要的参数),输出的信息包含了会编译哪些可执行程序:
1 | Programs: |
可以看到,并不包含ffplay。这是由于ffplay依赖sdl库,我本地环境没有,所以需要先安装sdl库。
在ffmpeg目录下执行./configure -h
,输出的信息可以看到这么一行:
1 | --disable-sdl2 disable sdl2 [autodetect] |
说明使用的是sdl2,并且如果当前环境有sdl2的话,会自动检测并使用。
这里我们选择源码安装sdl2:
1 | wget https://www.libsdl.org/release/SDL2-2.0.12.tar.gz |
再次回到ffmpeg目录下执行./configure
(携带好需要的参数),显示如下信息:
1 | Programs: |
说明会包含ffplay了,执行make编译:
1 | make -j8 |
编译完就可以看到生成好的ffplay等可执行文件了。尝试使用ffplay播放一个文件,一切正常。
最后,再贴一个macOS编译SDL1.2的方法,尽管我本次编译ffplay使用的SDL2,不需要SDL1.2,但是我在编译SDL1.2时也遇到了一些问题,把相关内容贴在这,留给需要的人。
实验环境: macOS Catalina 10.15.1
在 https://www.libsdl.org/download-1.2.php 这个网站找到源码下载地址。
下载SDL1.2.15,编译,安装:
1 | wget https://www.libsdl.org/release/SDL-1.2.15.tar.gz |
编译报错:
1 | ./include/SDL_syswm.h:58:10: fatal error: 'X11/Xlib.h' file not found |
缺少X11,直接下载dmg安装包文件,安装XQuertz,下载地址: https://dl.bintray.com/xquartz/downloads/XQuartz-2.7.11.dmg
再次尝试编译SDL,输出如下信息:
1 | ./src/video/quartz/SDL_QuartzVideo.h:94:5: error: unknown type name 'CGDirectPaletteRef' |
解决方法是把报错的那行代码注释掉。
再次尝试编译SDL,输出如下信息:
1 | ./src/video/x11/SDL_x11sym.h:168:17: error: conflicting types for '_XData32' |
我们修改报错的代码,在register后面增加_Xconst
,修改后如下:
1 | SDL_X11_SYM(int,_XData32,(Display *dpy,register _Xconst long *data,unsigned len),(dpy,data,len),return) |
再次尝试编译SDL,编译通过。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20043/
]]>macOS Catalina 10.15.1
的机器上,源码编译ffmpeg,生成的ffmpeg和ffprobe等所有可执行程序都无法正常运行。一运行就报段错误崩溃。就像这样:1 | ./ffmpeg |
即使是./configure
时不加任何参数也不行。
这已经是我第无数次被这个macOS 10.15
新版本系统坑了。。
在这个帖子的末尾,提供了解决方案,在./configure
加上--extra-cflags="-fno-stack-check"
即可,帖子的原文内容如下:
It seems the 10.15 toolchain turns on “-fstack-check” by default for Clang, like some Linux distributions do. You can see that here:
Stack checking is on by default on all platforms to prevent memory corruptions. (25859140)
Some discussion here traces it back to AVX, although turning off AVX (“–disable-avx”) didn’t seem to help building FFMPEG. (Nor, as skyzyx saw above, did turning off ASM entirely.)
Accordingly, adding “-fno-stack-check” to “CFLAGS” - f.ex., by ‘–extra-cflags=”-fno-stack-check”‘ when configuring works around the problem and builds a functioning FFMPEG.
Don’t quite know what that means for us. I imagine having stack checking on is desirable, but there’s at least a workaround while the source is further investigated.
另外帖子中也有关于用lldb调试ffmpeg,对应的崩溃信息,大家如果遇到同样的问题,也可以调起来确定下是不是这个原因引起的。
1 | lldb $(which ffmpeg) |
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20042/
]]>之前有前端同学问我,JavaScript中最大的数有多大。
那时就想写一些文章,从整型各种进制的转换,到原码反码补码的形式,最后到浮点数,再加上字符串类型的数字,把计算机世界里与数字相关的内容都说一说。
但由于对相关知识的掌握程度,表达能力,执行力等各方面原因,一直没有动笔。。
今天看到draveness大佬写了一篇《为什么 0.1 + 0.2 = 0.300000004 · Why’s THE Design?》,很好的讲解了浮点数的知识。
所以本文就直接在大佬文章的基础之上讲解就好了。看本文前可以先看看大佬的文章。
JS中Number类型的数字,不管是整数还是小数,底层都使用64位的浮点数形式存储。所以JavaScript中最大的数有多大,等价于64位浮点数最大的数有多大。
我们先跳出实现细节,来谈谈为什么浮点数存在精度问题。
比如0~100
这个范围内,整数的个数是有限的,就是101个。
而如果是小数,由于小数点之后的部分是无限的,比如我随便说两个小数,1.2304
和1.2300004
,中间到底出现多少个0都是合法的小数,所以理论上是没办法使用有限的存储空间(比如64位)表示完所有的小数。
你可能很容易想到,限制小数点后的位数,比如最多两位,也即范围是0.00~0.99
,那么0~100
范围内的数就变回有限了,也即101*100=10100
个。
这种方式适用一些场景,比如人民币,如果单位是元,那么小数只需要两位,分别是角和分。
可惜的是,并不是所有场景,小数点后保留两位就够用,关于这点相信也不用我过多举例,拿数字3.1415
来说,只能存储为3.14
或3.15
,也即精度丢失了。
并且,如果总是预留一部分空间存储两位小数,那么也是一种浪费。
抽象来看,我们面临的问题实际上是,如何用有限的空间存储尽量大的数字范围,以及尽量高的精度。
某种角度,浮点数是一种解决上述问题的编码方式。
JS和大多数编程语言一样,采用IEEE 754
浮点数标准。
在draveness的文章中,图文并茂的对该标准进行了描述,并分别举了0.1
,0.2
,0.15625
的例子。建议先看看那篇文章。
浮点数的公式是sign * power(2, exp) * (1 + fraction)
。
对于32位浮点数,sign占1位,exp占8位,fraction占23位:
[0, 255]
,0和255有特殊用途,我们不展开讲,那么还剩下[1, 254]
,由于浮点数除了支持特别大的数,还要取倒数用于支持特别小的数,所以exp有正有负,这8位的[1, 254]
会平移映射成[-126, 127]
的exp1/power(2, index)
,index从左到右取值为[1, 23]
,计算得到公式中的fraction我们补充看一些正整数的例子加深理解:
1 | 1 -> 1 * power(2, 0) * 1 |
在这个非常棒的网站中,你可以输入任意数字,查看对应的32位浮点数是如何表示的。
回到JavaScript中最大的数有多大
这个问题,这其实包含两个问题:
听着有点拗口,举个例子就明白了。假设某种表示方式只能存储1, 2, 3, 100
这4个正整数,那么第一个问题是100,第二个问题是3。
由于32位和64位浮点数的算法部分是一样的,大部分资料为了简洁,都采用32位讲解浮点数。
我们回到JS中的Number类型,底层使用的是64位浮点数,其中11位是指数部分,52位是小数部分。
指数部分11位,总共可表示2048个数字,范围是[0, 2047]
,刨去0和2047,剩下[1, 2046]
,再映射成[-1022, 1023]
。
对于问题一,指数部分和小数部分都取最大值,即
power(2, 1023) * (1 + 1/power(2, 1) + 1/power(2, 2) + ... + 1/power(2, 51) + 1/power(2, 52))
,结果会接近power(2, 1024)
。
注意,这里由于1023大于52,所以exp和fraction可以都取最大值,计算后的结果依然是整数。
对于问题二,实际上是受小数部分影响,即exp取52,fraction取最大值,也即
power(2, 53) - 1
,结果为9007199254740991
,这个数字有16位。
另外,JS中定义了一个常量Number.MAX_SAFE_INTEGER
,它的值就是9007199254740991
。
最后,我们再拿JS做个试验,验证下:
1 | > console.log(Number.MAX_SAFE_INTEGER) |
所以写JS的同学们要注意,Number超过这个值后,可能会出现bug哦。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20040/
]]>