阅读视图

发现新文章,点击刷新页面。
🔲 ⭐

构建属于自己的云游戏服务器

最近沉迷于暗黑4第四赛季,所以就在倒腾,怎样才能随时随地玩到暗黑4,掌机steam deck 我试过了,太重并且性能很差,已经被我卖了,于是折腾起了云游戏。

先来看看我的折腾成果:https://www.bilibili.com/video/BV1Z93TeuEQ4/

其实效果我没想到有这么好,在远程串流的情况,可以 1080p 60hz 几乎无卡顿的玩暗黑4,延迟只有20ms左右,配上我的手柄简直就是一个强大的掌机。

各种平台云游戏怎样了?

有了上面的需求之后,我就去试了以下几个平台:GeForce Now、Xbox Game Cloud、Start云游戏、网易云游戏。

但是遗憾的是,几乎每个平台都有自己的问题。首先上面列举到的所有平台,IOS 都只有网页版,因为苹果不让上架。

Start云游戏:我检查了一下,只有少数的几个单机游戏,大多是网游手游,并没有暗黑4;

网易云游戏:无论是快速启动还是普通启动,都非常的慢,估计要3分钟左右才能启动。并且我充值了一下玩了一下,网络倒是很流畅,延迟只有15ms左右,但是非常的卡,并且经常进入游戏界面死机,估计是机器性能不行,说实话我有点心疼我充值的10块钱了。

所谓的高配,其实性能很低:

image-20240623180606792

Xbox Game Cloud:微软自家的平台,好处是有了XGPU之后可以畅玩所有云游戏,最大的问题是服务器不在国内,所以连接延迟其实很高,并且常有波动,如果要玩Xbox Game Cloud 那么还需要充值XGPU才行;

Xbox Game Cloud 强大的游戏阵容:

image-20240623180512545

GeForce Now:这个云游戏平台可以说只能用无敌两个字来形容,每次登录都可以免费半个小时,即使服务器在海外,但是开了加速器也可以很稳定,画面有720P,在手机上玩还可以,主机性能也很好,经常分配到2080以上的机器。但是唯一不爽得是,免费用户每次都要排队挺长时间的,并且如果要付费,其实挺贵的,基本要100元每个月了。

GeForce Now价格表:

image-20240623180446219

开源解决方案

所以尝试了这么的云游戏发现都不好用之后,为什么不可以自己弄个呢?其实所谓的云游戏,无非是用客户端连接到主机端而已。那么我们实际上也可以把自己家里运行的PC或者主机,变成了由云游戏服务商提供的云上服务,这种行为就叫做串流游戏。

所以我们要做的是怎样把我们的私有网络的PC或者主机做成云服务提供给外网访问,让我们可以随时随地,只要有网就可以使用。

memory12

那么我的要求主要有这么几点:

  1. 要有跨平台的客户端,保证mac、iphone、android、win 都能用;
  2. 延迟要足够低,支持的可配置项要足够多;

正好自己有台闲置的 4090 的机器,那我就可以用它来作为主机端,我的安卓机作为客户端进行串流云游戏。

服务端 & 客户端

目前服务端主要有以下几个实现方案:

  1. N卡GeForce Experience
  2. Sunshine

客户端主要有:Moonlight

N卡GeForce Experience

如果你使用N卡,并且是GTX960以上可以通过GeForce Experience进行串流。只需要打开GeForce Experience在设置里找到SHELD这个串流配置,并添加游戏或者应用程序即可。

Featured image of post Nvidia Gamestream + Moonlight 如何串流桌面畫面

但是我现在不是很推荐这个方案,因为NV说过他们要把这个功能去掉,只是现在没有去掉而已。

Sunshine

它是一个开源推流方案 https://github.com/LizardByte/Sunshine , 属于通用串流方案,支持Nvidia、AMD、Intel。尤其适合核显串流(如果你用的是P106这类显卡没有视频编码器,只能使用Sunshine串流方案)。

下载Sunshine后首先需要运行服务端安装脚本install-service.bat,然后再运行sunshie。sunshine没有UI界面,设置需要通过网页端。运行sunshine后访问https://127.0.0.1:47990进行设置。设置里最重要的是进行PIN码配对,设备之间PIN匹配之后就可以进行串流了。

image-20230314210726196

Moonlight

Moonlight 以方便的将Windows电脑画面传输到各主流操作系统的客户端软件上,甚至可以直接传输至谷歌浏览器。画面方面,移动端最高支持4K120帧,且支持HDR(需要显卡支持),而桌面端甚至可以直接自定义分辨率和帧数;交互方面支持键鼠/手柄/触摸屏/触控板/触控笔,就像用自己的电脑一样使用远程电脑。该方案无广告,完全免费。

手机端可以在各大商店下载,也可以去 Moonlight官网地址:https://moonlight-stream.org 下载。如果使用iphone作为客户端,直接在App store下载Moonlight即可。

保持主机和客户端在同一局域网内,打开客户端软件,应该能够看到主机的计算机名。点击会弹出4位PIN码,需要在Sunshine配置网页 https://localhost:47990/pin 中输入PIN码。建立连接后,点击桌面(DESKTOP)将启动桌面串流。

网络配置好之后,在局域网内串流延迟通常相当的底,我经常躺床上用 pad 串流我书房的 pc 玩游戏,延迟只有几毫秒。

远程串流

由于Geforce Experience和Sunshine默认只在本地网络监听端口,客户端和主机位于同一局域网内才能连接成功,如果要真正实现远程连接,最简单稳定的方法是公网直连。

独一无二的IP地址使得主机能够在互联网中被识别,但是由于IPv4地址匮乏,大多数家庭网络并不具备公网IPv4地址。

所以我这里采用内网穿透的方式来构建我们的云服务:

Frame 2

内网穿透的核心思想就是“映射”和“转发”,把私有网络的设备的端口映射到公网设备的端口上,来进行流量转发。思想其实很简单,由于内网设备没有ip,那么我们通过一台有公网ip的机器来代替把流量做一层转发。比如上图,

我们在外网设置的用手机访问云服务器的 7000 端口,实际上云服务器会接收到之后通过47900进行转发到我们私有网络的pc机器,然后pc机器处理完之后再通过46900 端口转发给云服务器,上面所提到的端口都是可以自定义的。

那么对于做内网穿透一般现在流行两种做法:

  1. 直接 p2p 点对点的进行传输,流行的方案有 zerotier;
  2. 基于服务器的流量转发,流行的方案有 frp;

为什么会有内网穿透?

其实在互联网的世界中,如果每个用户都有真实的IP情况下,那么我们可以通过源IP+源端口+目标IP+目标端口+协议类型很容易的找到对方,是根本不需要P2P的,因为本来任何对象都可以作为Server或者Client来提供服务,彼此之间是可以互联。

但是IP和端口,是有限的,最初设计者也是没想到发展如此迅速,整个IPv4的地址范围,完全不够互联网设备来分配,那为了解决地址不够用的问题,就引入了NAT。

Frame 3

NAT(Net Address Translate,网络地址转换)是一种IP复用的一种技术,将有限的IP扩展成无限,由于IPv4地址资源有限,而NAT将网络划分成了公有网络和私有网络,允许多个设备使用一个公共IP地址访问互联网。路由器会将内部网络中的私有IP地址转换为公共IP地址,从而节省了IPv4地址资源。

所以我们在用 WIFI 的时候可以看到我们手机或PC上的IP地址通常是:192.168.x.xxx,这其实就是由路由器分配的地址,并不是真的地址。

另外,在 IPv4 地址资源越来越紧张的今天,很多电信运营商,已经不再为用户分配公网 IP;而是直接在运营商自己的路由器上运营 NAT,所以会出现甚至一整个小区共用一个 IP 出口的情况。

通过NAT技术的公私网络隔离,可以实现IP复用,解决了IPv4不够用的问题,但是也同时带来了新问题,那就是直接导致通信困难,由于NAT导致IP成为虚拟IP,外网无法针对内网某台主机进行直连通信,因为没有真实地址可用。

所以为了将NAT设备内外通信打通,就有了内网穿透技术。

zerotier

zerotier 是一个开源的内网穿透软件 https://github.com/zerotier/ZeroTierOne ,有社区版本和商业版本,唯一的区别是社区版本有 25台连接数量的限制,但对普通用户足够了,用它可以虚拟出一组网络,让节点之间的连接就像是在局域网内连接一样。

zerotier 底层是通过一个加密的p2p网络来实现连接。由于节点之间通常存在NAT隔离,无法直接通信,所以 zerotier 存在一个根服务器来帮助通路建设,所谓通路建设俗称打洞(hole punching),也就是穿透NAT隔离实现两个节点的连接。打洞也是区分 UDP 和 TCP 的,由于 zerotier 用的是 UDP,所以这里以 UDP 讲解打洞原理。

假设clientA 想要直接与clientB 建立UDP会话,用S表示根服务器:

A最初不知道如何到达B,因此A请求S帮助与B建立UDP会话,S会记录下他们各自的内外网IP端口:

Frame 4

打洞中:

S用包含B的内外网IP端口的消息回复A。同时,S使用其与B的UDP会话发送B包含A的内外网IP端口的连接请求消息。一旦收到这些消息,A和B就知道彼此的内外网IP端口;

当A从S接收到B内外网IP端口信息后,A始向这两个端点发送UDP数据包,并且A会自动锁定第一个给出响应的B的IP和端口;

B开始向A的内外网地址二元组发送UDP数据包,并且B会自动锁定第一个给出相应的A的IP和端口;

Frame 4

打洞后:

A和B直接利用内网地址通信

Frame 4

zerotier 的根服务实际上是部署在海外的,如果我们直接使用,很可能连不上,并且延迟基本在200ms以上,我们可以通过 zerotier-cli listpeers 查看根服务器:

# ./zerotier-cli listpeers
200 listpeers <ztaddr> <path> <latency> <version> <role>
200 listpeers 62f865ae71 50.7.252.138/9993;24574;69283 341 - PLANET
200 listpeers 778cde7190 103.195.103.66/9993;24574;69408 213 - PLANET  
200 listpeers cafe9efeb9 104.194.8.134/9993;4552;69462 159 - PLANET

上面的 PLANET 节点就是是ZeroTier网络中的根服务器。它们负责在对等点之间中继初始流量,帮助对等点建立对等连接,并充当身份和相关公钥的缓存。

我们随便 ping一下它的延迟:

# ping 50.7.252.138
PING 50.7.252.138 (50.7.252.138) 56(84) bytes of data.
64 bytes from 50.7.252.138: icmp_seq=1 ttl=46 time=347 ms
64 bytes from 50.7.252.138: icmp_seq=2 ttl=46 time=347 ms
64 bytes from 50.7.252.138: icmp_seq=3 ttl=46 time=354 ms

这样的游戏明显是玩不了云游戏的,zerotier 也考虑到这种延迟的情况,所以可以让有需要的用户自建 MOON 服务器。

ZeroTier中的MOON节点是用户定义的根服务器,可以添加到ZeroTier网络中。它的行为类似于ZeroTier的默认根服务器(称为PLANET节点),但由用户控制,我们可以把 MOON 服务器部署在离自己更近的地方,比如我就部署在广州,可这样以通过提供更近或更快的根服务器来提高网络性能。

怎样部署我这里就不贴教程了,可以自己去search一下,很简单。 但是现实场景中的网络要复杂的多,远远不是部署一个 MOON 节点就可以解决延迟的问题。通常我们的网络会涉及到防火墙限制、运营商级NAT、路由器兼容问题,还有就是 ZeroTier 走的是 UDP, 在国内的网络环境下一些运营商会对UDP流量实施QoS(服务质量)策略,,丢包可能会比较严重。

所以总之ZeroTier这条路并不是这么好走,看起来 p2p 直连貌似可以很美好,理论上可以不受根服务器的影响,两端直连跑满所有带宽,但实际上当不能打洞成功的时候那么就会退化成根服务器转发,那么实际的速率就取决于你自建的 MOON 节点的转发带宽了。

并且还有一个问题是,ZeroTier 是需要客户端的,到目前为止移动端的 app 是不支持添加自建 MOON 节点信息的,也就是说只能在电脑上进行串流,这实用性还是下降了不少。

所以总结一下优缺点:

优点:

  1. 组网非常方便,可以像局域网一样连接ZeroTier组网内的节点;
  2. 连接以及数据传输都是加密,所以比较安全;

缺点:

  1. 根服务器在海外,需要自建MOON,否则延迟很高;
  2. 依赖服务端,并且移动端app功能不完善;
  3. 受制于网络环境,p2p 打洞成功率低;

frp

frp 也是一个开源软件 https://github.com/fatedier/frp ,实际上它没有这么多花哨的功能,就是帮我们做了一个流量的转发。它的客户端连接不需要app,所以用来串流的话直接用moonlight直接连接frp远程转发服务器即可,可以说很方便了。

它的架构如下:

Frame 5

在安装frp远程转发服务的时候,我这里给一下配置,因为现在网上找的教程都是老的 ini 配置,现在新版本用的是 toml配置。

服务端的配置:

#frps服务监听的本机端口
bindPort = 9200
bindAddr = "0.0.0.0"
# frpc客户端连接鉴权token,默认为token模式
auth.token="xxxx" 

#日志打印配置
log.to = "./log"
log.level = "debug"
log.maxDays = 7

allowPorts = [
    // 远程连接需要用的端口
  { start = 47000, end = 48010 } 
]

sunshine主要连接的端口是这几个:

TCP 47984, 47989, 48010
UDP 47998, 47999, 48000, 48002, 48010

所以我们需要给这几个端口都加上防火墙,服务器和pc都要开放相应的端口,在测试的时候可以先全打开,测试完了再挨个加上,免得莫名其妙的问题。

pc端的配置:

#token需要与服务端的token一致
auth.token = "xxxxx"
# 服务端的公网ip
serverAddr = "1xx.xxx.xx.xx"
# 服务端的监听端口
serverPort = xxx

[[proxies]]
name = "47984"
type = "tcp"
localIP = "127.0.0.1"
localPort = 47984
remotePort = 47984

[[proxies]]
name = "47989"
type = "tcp"
localIP = "127.0.0.1"
localPort = 47989
remotePort = 47989

[[proxies]]
name = "47990"
type = "tcp"
localIP = "127.0.0.1"
localPort = 47990
remotePort = 47990

[[proxies]]
name = "48010"
type = "tcp"
localIP = "127.0.0.1"
localPort = 48010
remotePort = 48010

[[proxies]]
name = "47998"
type = "udp"
localIP = "127.0.0.1"
localPort = 47998
remotePort = 47998

[[proxies]]
name = "47999"
type = "udp"
localIP = "127.0.0.1"
localPort = 47999
remotePort = 47999

[[proxies]]
name = "48000"
type = "udp"
localIP = "127.0.0.1"
localPort = 48000
remotePort = 48000

[[proxies]]
name = "48002"
type = "udp"
localIP = "127.0.0.1"
localPort = 48002
remotePort = 48002

[[proxies]]
name = "48010"
type = "udp"
localIP = "127.0.0.1"
localPort = 48010
remotePort = 48010

pc端frp自动启动

windows系统开机自启比较麻烦,不像linux简单,所以为了保证windows后台运行 frpc,创建脚本 frpc.vbs,将以下内容粘贴进去:

set ws=WScript.CreateObject(“WScript.Shell”)
ws.Run “[frpc执行文件] -c [frpc配置]”,0

注意可能需要修改路径(默认路径是放C盘目录下)

将 frpc.vbs 放入 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 目录内,即可实现开机自启动。

远程唤醒(Wake On LAN)pc

家里的电脑如果经常开机的话很费电,所以按需开机是最佳办法,那么就需要远程登陆开机。远程唤醒需要主板的支持,现在的主板基本都支持。

首先我们要进入到主板的 BIOS 设置选项里面把 WOL 功能打开,具体方法视厂商而定,可以参考的关键词包括:

  • Automatic Power On
  • Wake on LAN/WLAN
  • Power Management
  • Power On by Onboard LAN
  • Power On by PCI-E Devices

然后在我们被唤醒的电脑里面找到网卡设置:

img

img

然后我们可以在内网尝试一下,是否可以唤醒成功,在应用市场随便找个WOL软件,填上内网被唤醒机器的IP地址和MAC地址即可:

img

外网唤醒,我们需要一个中间设备来中转我们的流量,因为我们需要被唤醒的机器已经被休眠了,是无法接收到请求的,所以我这里内网用我的软路由进行转发:

首先我们要做的就是 DHCP固定住自己内网PC的内网IP,要不然无法转发唤醒,通常可以在路由器里面设置:

image-20240629175131874

然后我是通过 OpenWrt 来和我远端服务器建立好 frp通信,监听转发端口,到时候外面的请求会先到 OpenWrt ,然后由它再转发给我的内网PC:

image-20240629175315251

最后如果觉得麻烦,其实可以用远程物理按键解决,一劳永逸:

image-20240626145854217

隐私屏 / 作为副屏

在用 sunlight 串流的时候由于显示的是桌面的,,因为串流软件会捕捉屏幕上的内容并编码成视频流。如果关闭屏幕,编码器将无法获取到需要的画面信息,导致串流中断。

那么如果我们想要关闭屏幕串流,那么可以用这个工具 https://github.com/VergilGao/vddswitcher ,通过 vdd 创建一个虚拟屏幕可以实现即使主屏关闭也能串流。

最后

游戏串流最后不仅满足了我在外网想要随时随地玩游戏的想法,并且还拯救了我的腰椎,在家里玩游戏现在基本是用平板串流到我的电脑上面,然后买个支架夹着我的平板,然后躺着玩,但愿各位游戏佬都能找到属于自己的游戏环境。

image-20240626152310201

Reference

https://github.com/VergilGao/vddswitcher

https://github.com/LizardByte/Sunshine

https://github.com/moonlight-stream/moonlight-qt

https://keenjin.github.io/2021/04/p2p/

https://sspai.com/post/68037

https://bford.info/pub/net/p2pnat/

扫码_搜索联合传播样式-白色版 1

构建属于自己的云游戏服务器最先出现在luozhiyun`s Blog

🔲 ⭐

作为开发需要了解 SSD 的一切

这篇文章主要来探讨一下SSD相关的问题,以及我们在写代码的时候如何更高效的利用好 SSD 的特性。

SSD 基本介绍

其实现在 SSD 已经很普及了,SSD 被称之为固态硬盘是相对于普通的机械硬盘 HDD 而言,因为它没有机械结构。普通的机械硬盘 HDD 一般都需要将执行器臂将读写头定位在驱动器的正确区域上以读取或写入信息。

A diagram of a hard drive

因为驱动器磁头必须在磁盘的某个区域对齐才能读取或写入数据,并且磁盘不断旋转,所以在访问数据之前会有延迟。HDD 的延迟通常以毫秒为单位,硬盘驱动器通常需要10-15ms才能在驱动器上找到数据并开始读取,即使是现在 15000 转的 HDD 延迟最低也要 4ms,IOPS(每秒的输入输出量) 在 200 左右,顺序读写速度在300MB/s 左右 。

反观我们看现在 SSD,以三星的 990 PRO为例,读写速度最高达到了 7000+ MB/s ,IOPS 高达 1,400K,延迟在 41us 左右,这么一对比,大家可以知道其实 SSD 比 HDD 快了好几个数量级。

SSD 内部结构

SSD 的内部结构一般是由三部分构成:1. Controller 控制器 ;2.DRAM缓存;3.NAND闪存;

image-20240207100920222 1

控制器是用来控制 SSD 的所有操作的,从实际读取和写入数据到执行垃圾回收和耗损均衡算法等,以保证SSD 的速度及整洁度;

DRAM缓存不是所有的硬盘都有,对于我们上面提到的 990 PRO 来说会有 2GB 左右的缓存,主要是用来存放的是逻辑物理映射表,控制器会通过缓存的映射表来查找数据使用。还承载了一部分待写入的数据,等够一页的时候一次性写入到NAND颗粒里面,用来缓解写入放大;

顺带提一下无缓的固态硬盘也不用过于担心性能问题,一般来说会使用 HMB 技术通过PCIe通道找内存借用一部分内存空间来存储映射表,在每次开机的时候往内存写入部分常用的FTL表,这个空间大小通常是64MB。像我们国产的致钛TiPlus7100就是采用的无缓+HMB机制,其速度也不差。

NAND 闪存是最终用来存放数据的地方,它不仅决定了SSD的使用寿命,而且对SSD的性能影响也非常大。比如大家所熟知的 SLC, MLC, TLC 闪存。NAND本身由所谓的浮栅晶体管组成,它是一种非易失性存储器,即使不通电也能保持状态,NAND 由一个个浮栅晶体管堆叠而成。根据每个浮栅晶体管可以保存1bit, 2bit, 3bit数据量可以分为SLC, MLC, TLC。

Frame 3

1个Page 一般是4KB 或者 8 KB ,上面我们提到的 990 PRO 是 16KB, 不同 SSD 大小也不同。因为浮栅晶体管被分组并以非常特定的属性访问,所以我们只能按页来进行数据的读写,如下简化图中红色的每一行都代表一个 Page ,写数据的时候会给行列两端加上不同的电压,加电压只能加到行和列上,所以没法控制到单个浮栅晶体管;

image-20240207163929198

Block 是擦除的基本单位,每个 Block 会包含 128 到 256 个上面这样的 Page。如下图由于一块 Block 上面的所有浮栅晶体管其实是共用一个衬底,只要给这个衬底施加高压,浮栅晶体管里面存储的数据就会被清空,所以 Block 是擦除数据的基本单位。

image-20240207165403915

为啥 SSD寿命是有限的?

其实这取决于浮栅晶体管的结构,它的结构下图所示,我不打算概述它的原理,但是我们可以知道在浮栅晶体管中电子是存储在浮栅层的,读写数据的时候电子需要穿过隧穿层,由隧穿层来锁住电子,隧穿层穿越次数是有限制的,进出次数多了,就锁不住电子了,那么该 Block 就没法擦写数据了。

Frame 4

所以如果一直在同一个 Block 擦写数据,这个 Block 的寿命很快就会消耗完,为了有效的延迟 SSD 的使用寿命就很考验 Block 的管理技术了,后面我们可以看看现在的 SSD 有哪些管理抓手。

读、写、擦除

当首次向SSD写入数据时,因为数据都处于已擦除的状态,所以数据可以直接写入,至少一次性写入一个 Page。因为在写入的时候会按照 Page 为单位进行写入,所以如果要写入的数据小于一个 Page,那么其余写入超过必要的数据称为写入放大

只有空闲的Page才能被写入,并且是不能覆盖的,所以在修改数据的时候数据会被写入到一个空闲页面中,一旦被持久化到页面上,原来的旧页面会被标记成 stale 状态,表示它可以被擦除。

所以一个 Page 变成 stale 状态之后唯一能让它们再次使用的方式就是擦除它们,但是擦除的维度我们上面也讲过了,是以 Block 为维度进行擦除的,并且擦除命令不由用户控制,擦除命令是由SSD控制器中的垃圾回收机制进程在需要回收陈旧页面以腾出空闲空间时自动触发的。

除了上面提到的写入放大以外,如果在写入数据的时候不是以 Page 为单位进行写入,会导致Page 被修改并写回驱动器之前被读入缓存,这种操作被称为读-修改-写,比直接操作页面要慢。

所以我们写入数据的时候应该要对齐写入,也就是按 Page 整倍大小写入。并且为了最大限度地提高吞吐量,尽可能将小写入保留到 RAM 中的缓冲区中,当缓冲区已满时,执行一次大写入以批处理所有小写入。

垃圾回收

数据以 Page 为单位写入到存储中。然而,存储器只能以较大的单位 Block 擦除。如果不再需要一个Block 中某些Page内的数据,仅会读取该块中含有有效数据的Page,并重新写入到另一个先前擦除的空 Block 中,然后将原来的 Block 进行擦除,擦除之后的 Block 又可以重新使用,这个过程叫做垃圾回收。

所有的SSD都包含不同程度的垃圾回收机制,但在执行的频率和速度上有所不同,垃圾回收占了SSD上写入放大的很大一部分。

ssd-writing-data

比如上面这个例子中,在 Block 1000 中写入 x,y,z 三个数据,然后想要修改 PPN 为 0 的这块数据时由于不能覆盖,所以只能在 PPN 为 3 的 Page 上面写入 x` 。当后台的垃圾回收进程启动的时候会将有效的数据拷入到另一个空闲的 BLock 中,然后将原来的 Block 1000 中的数据擦除。

磨损平衡

SSD 擦除写入的次数也叫做 P/E 周期( program/erase cycle),由于 P/E 周期是有限的,所以假设SSD其中数据总是从同一个精确的块中读取和写入,那么这块 Block 很快就达到了使用寿命的上限,SSD控制器会将其标记为不可用。然后磁盘的整体容量会下降。

所以实现磨损均衡(wear leveling)是控制器的主要目标之一,即在块之间尽可能均匀地分配P/E周期,理想情况下,所有块都将达到其P/E周期限制并同时磨损。

我这里用长江存储致钛的图来解释一下,上面三张是没有磨损均衡的,那么反复写就会出现坏页,下面三张是有磨损均衡,那么控制器会根据不同的算法挑选出擦写次数较低的 Page 进行写入。

image 3

当然我们也知道,理论和现实总是有区别的,具体实现起来还是很难的。因为想要磨损均衡,那必须要写入的时候将一些Block的数据进行移动,这一过程本身会导致写入放大的增加。因此,块管理是最大化磨损均衡和最小化写入放大之间的权衡,也就是它是一个 trade-off 的艺术。

很蓝的啦是什么梗

磨损均衡分为静态磨损均衡与动态磨损均衡。动态磨损平衡是指当需要更改某个Page中的数据时,将新的数据写入擦除次数较少的物理页上,同时将原页标为无效页,动态磨损平衡算法的缺点在于,如果刚刚写入的数据很快又被更新,那么,刚刚更新过的数据块很快又变成无效页,如果频繁更新,无疑会让保存冷数据的Block极少得到擦除,对闪存整体寿命产生不利影响。早期的SSD主控多用动态磨损平衡算法。

静态磨损均衡是在每次写入时从空白Page中挑选擦写次数最少的进行写入,并且为了防止冷数据的Block极少得到擦除,静态磨损均衡会在条件具备的情况下搬走长期占用Block的不变数据,将其释放出来用于新数据写入,从而避免过度消耗其他闪存单元的寿命。

预留空间

预留空间 Over-provisioning,也叫OP,指的是 SSD 一般会保留一部分的空间对用户不可见,大多数的厂商都会预留7%到25%左右的空间,额外的预留空间有助于降低控制器写入闪存时的写入放大。用户可以通过将磁盘分区为低于其最大物理容量的逻辑容量来创建更多的过度配置。例如,可以在100 GB驱动器中创建一个90 GB分区,并将剩余的10 GB预留不使用。

比如下面的这种图中, 是我找到金士顿官网的预留空间图:

image-20240208170543909

预留空间主要是给 SSD 控制器使用,一方面是用来做是用来做磨损平衡,替换可见空间中被磨损的 Block,另一方面是用来提升性能使用;

因为上面也提到了,SSD 的写入是无法覆盖的,只能在空白页面上写入,所以在这个过程中预留空间足够大则从空白的页中进行写入,可以减少擦除次数也能够减少有效数据的再次写入,降低 Block 的磨损,从而提升硬盘的耐用性。

预留空间在读写工作负载很重的时候可以充当NAND闪存块的缓冲区,帮助垃圾回收机制处理吸收写入高峰。因为垃圾回收擦除数据比写入要花费更多的时间,垃圾回收一般在空闲的时候做,如果在负载很重的时候垃圾回收来不及擦除,那么可能会和写入操作同时进行,而预留空间可以作为缓冲区给垃圾回收机制留出足够的时间来赶上并再次擦除块。

另一方面预留空间也可以减少垃圾回收次数,空白页面多了自然就不用频繁的做垃圾回收了,这同时也减少了写入放大。

Trim

简单来说,TRIM主要是优化固态硬盘,解决SSD使用后的降速与寿命的问题。

原本在机械硬盘上,写入数据时,Windows会通知硬盘先将以前的擦除,再将新的数据写入到磁盘中。而在删除数据时,Windows只会在此处做个标记,说明这里应该是没有东西了,等到真正要写入数据时再来真正删除,由于这个标记只是在操作系统层面,SSD 并不知道哪些数据被删了,所以我们日常的文件删除,都只是一个操作系统层面的逻辑删除。这也是为什么,很多时候我们不小心删除了对应的文件,我们可以通过各种恢复软件,把数据找回来。

SSD 只有当操作系统在同一个被删了的 Block 里面写入数据的时候才知道这个数据已经被删了,那么才会把它标记成废弃,等待垃圾回收。。这就导致,我们为了磨损均衡,很多时候在都在搬运很多已经删除了的数据。这就会产生很多不必要的数据读写和擦除,既消耗了 SSD 的性能,也缩短了 SSD 的使用寿命。

Frame 5

比如上面这个图中,我们在系统层面删除了程序3,但是在SSD 的逻辑块层面,其实并不知道这个事情。这个时候,如果我们需要对 SSD 进行垃圾回收操作,程序3占用的物理页仍然要在这个过程中,被搬运到其他的 Block 里面去。只有当操作系统在里面重新写入数据的时候SSD才会把它标记成废弃掉。

TRIM指令让操作系统可以告诉固态驱动器哪些数据块是不会再使用的;否则SSD控制器不知道可以回收这些闲置数据块。TRIM的简约性将极大减少写入负担,同时允许SSD更好地在后台预删除闲置的数据块,以便让这些数据块可以更快地预备新的写入。

TRIM命令只有在SSD控制器、操作系统和文件系统支持它的情况下才有效。

并发性

来看并发性之前,先来看看 SSD 具体的层级结构,SSD 使用层级的方式管理NAND Flash。Controller会使用多Channel的方式与NAND Flash Chip连接。每个Channel可以被视为一条数据传输路线,多个通道可以让控制器同时读写更多的数据,提高数据传输的速度和效率。每个NAND Flash Chip内部封装(Packaging)了多个Die,每个Die上排列了多个Plane,每个Plane中包含多个Block,每个Block中有多个Page。

Frame 6

  • Channel级别:Controller可以同时使用多个Channel在不同的NAND Flash Chip上执行不同的操作
  • Chip级别:连接在相同Channel上的不同的不同Chip,也可以使用流水线(Interleaving)的方式同时执行不同的命令
  • Die级别:一个Chip封装多个Die,Chip可以把不同的命令发送到多个Die上并行执行
  • Plane级别:一个Die包含多个Plane,相同的命令(读、写、擦除)可以在一个Die的多个Plane上同时执行

像我们上面提到的 990 Pro 这款 SSD,有 8条 Channel,每条Channel可以传输2,000 MT/s,MT/s代表每秒百万次传输(MegaTransfers per second)。每个 Chip 里面有 16 个 Die,每个 Die 有 4 个 Plane。

写的并发性

所以通过上面信息可以知道,SSD 是可以并行写入多个 Block 的,所以利用 SSD 多级并行机制,可以被并行访问的最大数量的 Block 集合称为一组 Cluster Blocks,它的可以从几十MB到几百MB,具体取决于控制器策略、SSD 容量、NAND 闪存类型等,不过我查过了,一般厂商没有给出这个具体的大小。

利用Cluster Blocks的并行性能,SSD可以把一次大块写打散到多个Cluster Blocks上并行执行,有效降低时延,提高吞吐量。

如果写操作每次写入的数据量小于 Cluster Blocks 大小时则顺序写的吞吐量显著高于随机写,所以如果随机写入既是Cluster Blocks 的倍数,那么它们的性能与顺序写入一样好。两者的区别主要体现在以下两个方面:

  • 小块写入会导致更多的映射表更新,而顺序写可以合并映射表的更新;
  • 小块随机写更容易造成某个Block中少量的Page被设置为无效,需要进行擦除,加剧垃圾回收过程的写放大;

最后,关于并发,其实用一个线程写入一大块数据和用许多并发线程写入许多较小块数据一样快。事实上,一个大写入保证了SSD的所有内部并行性都被使用。因此,尝试并行执行多个写入不会提高吞吐量。然而,与单线程访问相比,许多并行写入会导致延迟增加。所以一次大写入比多次小并发写入要好。

再来就是当写入很小并且无法合并的时候,多线程是有益的,许多并发的小写请求将提供比单个小写请求更好的吞吐量。所以如果I/O很小并且无法批处理,最好使用多个线程。

总结就是这样:

大块写 小块写
单线程 最快 最慢
多线程并发 无助于吞吐量,提高时延 有助于提高多级并发机制的利用,提高吞吐量

读的并发性

同样的为了提高读取性能,最好将相关数据一起写入。因为读取性能是写入模式的结果。当一次写入一大块数据时,它会分布在单独的NAND闪存芯片上。因此,您应该在同一页、块或集群块中写入相关数据,这样以后就可以利用内部并行性,通过单个I/O请求更快地读取这些数据。

Frame 7

因为写入的时候SSD会尽量的把所写的Page打散到多个Clustered Block上进行并行,那么读操作如果同时读取这些Page将会获得最多的并行。相反如果读操作总是读取不同写操作写入的Page,那么这些Page很可能处于相同的Plane上,必须串行读取。如上图所示。

对于多线程并发小块数据读取来说,性能是不如单线程大块数据读取的,因为这不能用到预读机制,这种机制通过预先读取可能接下来会被请求的数据到缓存中,减少了数据访问的等待时间,从而提升了读取性能。

在编程时如何更好的利用SSD

更好的利用SSD可以获得一些免费的好处,如:提升应用程序性能、延长SSD的寿命、提高SSD的 IO 效率等等。

区分冷热数据

假设我们将冷热数据混合排布在同一个区块,对于 SSD 来说,如果要修改其中的一小块内容(小于 1 页),SSD 仍然会读取整页的数据,这样会导致写入放大。所以对于一些改动非常频繁的热数据应该尽可能的 cache 住在内存里,然后批量的进行更新,这样不仅更快,而且还能提升SSD寿命。

采用紧凑的数据结构

这其实也和 SSD 的结构相关,更紧凑的数据结构可以尽量让数据都聚拢在同一个 Block 中,减少 SSD 的读取操作,同时也能更好的利用缓存。同样的,由于SSD 写入方式的特殊性,紧凑数据结构将关联数据放置到相邻区域,减少可能的垃圾回收的同时,还能够降低写入放大带来的问题。

写的数据最好是页大小的倍数

避免写入小于NAND闪存页面大小的数据块,以最大限度地减少写入放大并防止读取-修改-写入操作。目前页面的最大大小为16 KB,因此默认情况下应使用该值,此大小取决于SSD型号。

当数据不足一页时为了最大限度地提高吞吐量,尽可能将小写入保留到RAM中的缓冲区中,当缓冲区已满时,执行一次大写入以批处理所有小写入。

将相关数据一起写入

读取性能是写入模式的结果。当一次写入一大块数据时,它会分布在单独的NAND闪存芯片上。因此,您应该在同一 Page、Block或 clustered block 中写入相关数据,这样以后就可以利用内部并行性,通过单个I/O请求更快地读取这些数据。

尽量避免读写混合

由小交错读写混合组成的工作负载将阻止内部缓存和预读机制正常工作,并将导致吞吐量下降。最好避免同时读取和写入,并在大块中一个接一个地执行它们,最好是clustered block的大小。例如,如果必须更新1000个文件,您可以迭代文件,对文件进行读写,然后移动到下一个文件,但那会很慢。最好一次读取所有1000个文件,然后一次写回这1000个文件。

不要总以为随机写入比顺序写入慢

如果写入很小(即低于clustered block的大小),则随机写入比顺序写入慢。如果写入既是clustered block的倍数,又与clustered block的大小对齐,随机写入将使用所有可用的内部并行级别,并且执行与顺序写入一样好。对于大多数驱动器,集群块的大小为16 MB或32 MB,因此使用32 MB是安全的。

大型单线程读取优于许多小型并发读取

上面我们也提到了,并发随机读取不能充分利用预读机制。此外,多个逻辑块地址可能最终在同一芯片上,没有利用或内部并行性。大型读取操作将访问顺序地址,因此能够使用预读缓冲区(如果存在)并使用内部并行性。因此,如果用例允许,最好发出大型读取请求。

大型单线程写入优于许多小型并发写入

大型单线程写入请求提供与许多小型并发写入相同的吞吐量,但是就延迟而言,大型单线程写入比并发写入具有更好的响应时间。因此,只要有可能,最好执行单线程大型写入。

当写入量很小并且无法分组或缓冲时,才使用多线程写

许多并发的小写请求将提供比单个小写请求更好的吞吐量。所以如果I/O很小并且无法批处理,最好使用多个线程。

对于读写负载很高的工作,应该配置更大的预留空间

预留空间有助于磨损均衡机制应对NAND闪存单元固有的有限生命周期。对于写入不那么重的工作负载,10%到15%的过度配置就足够了。对于持续随机写入的工作负载,保持高达25%的过度配置将提高性能。过度配置将充当NAND闪存块的缓冲区,帮助垃圾回收机制处理吸收写入高峰。

Reference

https://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/

https://arstechnica.com/information-technology/2012/06/inside-the-ssd-revolution-how-solid-state-disks-really-work/

https://www.bilibili.com/video/BV1aF411u7Ct/?vd_source=f482469b15d60c5c26eb4833c6698cd5

https://www.extremetech.com/gaming/210492-extremetech-explains-how-do-ssds-work

https://www.bilibili.com/video/BV1644y157mB/?vd_source=f482469b15d60c5c26eb4833c6698cd5

https://zh.wikipedia.org/wiki/%E5%86%99%E5%85%A5%E6%94%BE%E5%A4%A7

https://time.geekbang.org/column/article/118191

https://aerospike.com/docs/

https://www.techpowerup.com/ssd-specs/samsung-990-pro-2-tb.d862

扫码_搜索联合传播样式-白色版 1

作为开发需要了解 SSD 的一切最先出现在luozhiyun`s Blog

🔲 ⭐

2023年总结:保持心情愉悦&积极向上

hi,大家好呀,这是我第五年做年度总结了。

2022年总结

2021年总结

2020年总结

2019年年度总结(哈哈,19年我还将博客写在博客园)

我们永远不知道明天会发生什么,但是我们永远有保持开心的权利,这是这一期的主题。

有关学习

AI & 强化学习

在今年上半年,由于 AI 画图的兴起,让我感觉这个东西也挺有意思的,先后看了一些资料去学习 AI,这是我以前没有尝试过的领域,然后也写了两篇有关强化学习的文章:

写给开发同学AI强化学习入门指南 https://mp.weixin.qq.com/s/sTBFnSLS-WBjMWDokH2o9A

如何用 PPO 算法让 AI 学会玩 FlappyBird https://mp.weixin.qq.com/s/5DYBCCU3xsmTHtN5Ciz0WA

下面是我当时的学习计划:

  1. 如果和我一样一点基础也没有,并且概率论和线性代数的知识差不多都忘完了,那么可以去看一下相关课程学习一下,如果不关注公式啥的,这一步可以先忽略,大约周末一天时间就可以搞定;
  2. 然后如果对机器学习也一点基础都没有的话,可以先看吴恩达的课程,有个大致的理解,然后去看李宏毅的课程作为补充,如果单纯的想入门学习强化学习,那么只需要看前几节讲完神经网络那里就差不多了,这个视频课程估计要看 25 小时左右;
  3. 学完之后可以跟着《动手学深度学习 https://zh.d2l.ai/ 》一起动手学习一下我们上面学到的概念,写写代码,如果只是入门的话看前五章就好了,本篇文章的很多资料也是整理自这本书,大约 10 小时左右;
  4. 接下来可以看看 B 站王树森的深度学习的课程,可以先看前几节学习一下强化学习的基础知识点,大约 5 小时左右;
  5. 到这个阶段估计还是懵的,需要去上手做点项目,那么可以看《动手学强化学习》这本书,已经开源了 https://hrl.boyuai.com/ ,只看到DQN的部分,大约十几小时。

我是差不多花了小半年的时间学习并入门的,上面的学习步骤大家感兴趣的可以参考一下。

英语

今年十一去了趟日本旅行,旅行过程中发现自己的英语口语水平仅限于蹦单词,学了这么多年的英语有种白学的感觉,感觉我应该可以更好点,然后就开始各种练习口语以及听力。

我总结了一下我以前之所以没有学好英语是因为没有确定个目标,没有目标那就没有动力,所以这次我给自己定一个小目标,那就是先初步确定自己想要达到的雅思成绩。

定好了目标之后就想着怎么去达成,规划一下。首先要正视自己的问题所在,雅思不像以前的英语考试,考的是应试能力,它考的是全面的英语水平,所以我觉得首先是要提升自身的认知水平,应该要怎么学习才是有效的,可以看这两个视频:

【高能干货】这个视频将会颠覆你对英语学习的认知——听说篇 https://www.bilibili.com/video/BV1tf4y1s7NN

【高能干货】这个视频将会颠覆你对英语学习的认知——总述·阅读篇 https://www.bilibili.com/video/BV1aD4y127GE

听力

一开始我是找一下雅思的听力材料来听,对着材料反复听,反复默写,直到听明白为止,后面发现实在是有点无聊,这么枯燥的训练不是说坚持不下去,而是可以想办法让学习的过程更加有趣点。后面我就想起了最近上线的游戏《重返未来:1999》。

《重返未来:1999》是全英伦配音,每节的篇幅也不长,制作的也很用心,配乐也很好听,所以很适合用来做听力,并且可以一遍玩游戏一遍过剧情。

说实话我以前从来没有对一款游戏的剧情如此上心,因为需要练习听力的原因,需要反复听写,所以看了很多遍。需要注意的是,用这个素材来做听力,不需要到游戏里面去听,去b站听就好了,我听的是这个合集:https://www.bilibili.com/video/BV1dh4y1s7gg ,共100多 P 还在持续更新中。

里面我还是很喜欢斯奈德这个角色的,糯糯的声音,并且很有能力,独自一人支撑起自己的家族,可惜死了。

Frame11

口语

口语部分其实是不太好处理的,我以前也试过很多方法,包括请老师和我对话,但是效果都不怎么好,究其原因是因为自身的输入不够,背的单词和短语并没有内化到自己的语言体系中,导致一些你在阅读的时候觉得很简单的句子,但是你就是说不出来。

首先我觉得应该是要提升自己的句法量,短语量,我选择的是这个材料,简单,并且很有趣,里面会覆盖各种各样的场景:

EnglishPod全新版【1-365期全文本+讲解】英语听力口语学习 https://www.bilibili.com/video/BV1w7411g7jn

然后还有这个课程,通过拆解电影或电视剧里面的句子,简单易懂的告诉你如何连续,如何弱读。很多时候我们听不懂是因为没理解弱读和连读,很多时候正式交流中并不会读出整个单词。

Learn American Pronunciation through English Conversation https://www.youtube.com/playlist?list=PLrqHrGoMJdTQjpAE9LLYcpGqFOVQOyT7n

Frame12

AI 学习

chatgpt 练习口语

其实我也写过一篇文章,讲怎么使用 chatgpt 来练习口语以及写作了:

How to become a prompt engineer https://www.luozhiyun.com/archives/814

GPT其实也可以语音对话,那么是否可以用这个功能练口语呢?并且可以控制语音语速,你可以让它说快点,说慢点,等等。

Frame13

不过 chatgpt 就是有个问题,需要调教,如果不使用 prompt 直接和它聊,那么可能只是个夸夸机器人,或者无头苍蝇瞎聊。幸好的是 GPTs 开放出来了,大家可以直接使用它就行了,不需要再学习 prompt,比如这里我就搞了个 GPTs 的口语老师:

https://chat.openai.com/g/g-JnDEDhPgo-english-instructor

然后我还去找了一些雅思口语素材话题,每次我就翻一下书看看我要和它聊什么:

Frame14

然后我会和它说一段话关于这个主题,然后 GPT 会帮我分析我的句法问题,并告诉我怎么说才是正确的,这一点其实是比我的外教要好的,因为我的外教并不会每次都指正我的问题:

Frame15

然后我会让它结合我的观点,给我一个完整的例子,这样我可以自己维护起来背诵一些句型,这点尤其重要:

Frame17

chatgpt + notion 自动记单词

我在使用的是 python 调用 notion api + chatgpt 生成的表格,每当我有一个生词不认识,然后就输入一个单词,然后帮我填充上面的表格,全自动的,如下:

Frame16

并不是说记单词软件不好用,而是有时候我们在很多地方遇到的生词,想要维护自己的生词表,可以采用这种方式,并且可以自定义你想要啥都可以让 gpt 帮你生成,更加灵活。

关于用 AI 学习这方面我真的可以写好多技巧,比如我的强化学习也是有一部分是AI教我的,这里先这样,有机会再聊聊别的学习技巧。

其他

除了上面两点,我也看了一本让我感觉很厉害的书《designing-data-intensive-applications》,也就是 DDIA,如果能全部搞懂,相信你对数据库的了解会变得非常深入。当时这本书我才看到第二章就发现太牛逼了,以一种数据库总览的视角介绍了一遍一些数据库查询语言使用场景。当时读完之后我也是写了一篇总结文章:

数据库的数据复制与分区 https://www.luozhiyun.com/archives/805

但是也只有这一篇,因为原书功底实在深厚,我担心我总结不到位,并且网上搜了一下,总结的人实在太多,所以就没继续写了。

除此之外就是写了一些 C++ 相关的文章:

写给[C++ ]新人智能指针避坑指南 https://www.luozhiyun.com/archives/762

C++ 中让人头晕的const & constexpr https://www.luozhiyun.com/archives/756

C++ 中复杂却很有意思的SFINAE技术 https://www.luozhiyun.com/archives/744

C++ 中让人头晕的 typedef & typename https://www.luozhiyun.com/archives/742

学了这么久的 C++ 感觉还是很难用好它的,C++ 发展了30多年,历史包袱实在是太重了,太灵活了,导致各种特性,各种语法糖,让人感觉犹如天书,感觉如果真的有兴趣的话,这门语言是可以研究一辈子的。

分享点好物

apple watch ultra2

上一个我用的手表是 apple watch4,不知不觉已经用了5年的它,迎来了使命的终结,我决定给自己换一个新的手表:apple watch ultra2。

虽然我知道它又重,又大,还没有啥新功能,缺点很多,但是我买了它之后真的开心了很久,我几乎每次健身,每次游泳都会使用它的健身功能,每晚都会带着它睡觉帮我检测睡眠。

202311

大家可以感受一下,它真的大了不少:

2023

不过大也有大的好处,能看的文字更多了,很多长信息可以直接一屏就看完,不需要滑动。还有就是手表真好看啊,纯钛的合金戴着也很舒服。

老的apple watch4手表在完成它的使命之后现在在我桌面上当一个钟:

Frame 2

健身腰带

我原来的腰带已经用了很多年了,今年就想着自己已经练了七八年了,没有拥有过一条好的腰带,就想着给自己买一个好的腰带。一开始其实是看上了吕小军的店里面的腰带 CT800,买来后试了一下就发现实在是太硬了,并且还卡腰,不太适合我,就退了换了国外的卡迪罗。

这条腰带吸引我的点是因为它宣传纯手工牛皮打造,并且很多奥赛选手都在用它,但是买了用了一段时间后发现,质量实在是堪忧,没多久就开始开胶:

20231 2

后面健身群里有一个大佬,说自己很久没练了,想要出 SBD 腰带,我就收了,这一条也就是我现在最常用的腰带,这条腰带其实不是健身腰带,是一条力量举腰带,它和普通腰带不同的是它是等宽的。使用它的感觉就是它的包裹性真的比卡迪罗强太多了,如果想要稳定的支撑做硬拉和深蹲,这个腰带绝对是不二之选。

刚买了的适合前主人太久没用了,上面甚至有灰,感觉这位兄弟让我有机会体验这款腰带:

Frame 3

4090显卡

在年初的时候,我以学 AI 为由,给自己买了一张 4090显卡,当时这个卡买来的时候还怕亏很多,反复问自己真的需要一张这样的显卡吗?

后面的事情大家都知道,AI 的热度不断升温,曾经一度让这张卡一卡难求,后面甚至有禁令的加持,让这张卡即使我用了快10个月了,还能以高出买入价格的30%卖出,不愧第一神卡 (。A。)

Frame5

你要说用它值不值嘛,我觉得肯定是值的,用它玩过一些画图模型,视频模型,搞了一些蛮有意思的图片,后面还用它通关了《最后生还者》,不禁感慨,有生之年能玩到款游戏真好。后面还用它学习了强化学习,自己也写了一些文章: 《写给开发同学AI强化学习入门指南 https://mp.weixin.qq.com/s/sTBFnSLS-WBjMWDokH2o9A

kobo libra 2

买这款阅读器,是因为我的kindle实在是太老了,想要个有实体按键的阅读器但是 kindle oasis 又太久没更新了,正好10月去日本旅行的时候正好去试用了一下这款阅读器就以差不多1050的价格入手了(国内要1400左右)。

img

这款阅读器给我的第一感觉首先是颜值真的很难打,文字阅读的清晰度是真的很高,我在商场里和 kindle oasis 对比了一下,应该不相上下。然后就是它的按键翻页配合上它的人体工学的造型,握持起来真的很舒服,喜欢阅读的朋友不妨试一下。

Frame6

尝试点不一样的

多关注一下自己的心理健康

我发现自己很多时候内心充满了 chaos,以前不认为它是个问题所以长期得不到解决,但是今年开始我开始正视他们,不面对就永远得不到解决,我分解了一下我目前存在的问题,主要拆解为两部分:睡眠不好带来的疲倦感和内心的焦虑带来的不安感。

睡眠

首先来看看睡眠,在偶然间发现一本书:《why we sleep》,读了之后才发现原来睡眠和心理健康是相辅相成的,没有好的睡眠,一般心理健康就不会好,感觉这本书要是早20年被我看到就好了,不过这也是不可能的,《why we sleep》2017年才出版。

然后再说说我是怎么做的,总结起来我觉得有这几点建议是有用的:

  1. 冥想正念,这个下面再说;

  2. 睡前一小时调暗灯光,避免看手机,这样做主要是让身体有一个环境可以自己释放褪黑素,可以看看 kindle 或纸质书,我最近在看《禅与摩托车维修艺术》,这本书很厚,没什么情节,没什么逻辑,不需要理解,很好睡;

  3. 保证卧室不透光,这样不至于太早自然醒,因为我们的身体会在早上有光的情况下开始慢慢的降低褪黑素的水平,即使你睡眠不够,也会把你弄醒,所以保证卧室不透光可以让我们在睡够的时候醒来;

  4. 保持适量运动,但是不要在晚上运动,很多人白天没时间运动所以只能拖到晚上运动,这样我们会长时间处于亢奋状态下,晚上很难入睡,最好的运动时间其实是下午4点左右,其次是早上;

  5. 可以适当的在晚上睡前吃点锌镁片,可以促进睡眠,并且比褪黑素更加较健康安全;

  6. 空调温度开低一点,低一点真的会更加容易入睡,《why we sleep》里面有实验研究,想了解可以看看,但是注意不要感冒了;

  7. 不要看到时间,避免因看到时间导致的减缓焦虑;

  8. 躺床上长时间睡不着的时候,不要硬睡。可以慢慢起身,坐在床边,做做正念练习,缓缓的喝点点温水,调整一下思绪,尝试和自己说:“白天我已经想的足够多了,现在我应该安静下来”,再尝试入睡;

  9. 从下午开始不要喝任何有咖啡因的饮品,包括:奶茶,可乐,红牛等等。咖啡因的半衰期大约是3-4个小时,如果下午喝那么在睡前实际上还残留了蛮多咖啡因在体内;

实践中放弃使用的方法:

  1. 泡脚,这个实在麻烦,并且天气很热也不想泡,每次泡完之后还要倒水,清理泡脚桶,感觉是个体力活;

  2. 睡觉时听轻音乐,因为不可能整晚都在耳边放音乐,所以只能定时放,这让我很焦虑,总是在想:“我咋还没睡着,这个音乐又要停了”。

冥想

我试过很多正念课程,付费的有暂停实验室,还有b站各种免费课程,综合下来觉得李冉的这个课最好:https://www.bilibili.com/video/BV1NM4y1d7aC/ ,当然他也有付费课程,也不错的。每天睡前一小时正念15分钟左右,可以让自己在睡觉的时候保持一个纯净的思想环境,更容易入睡。

看了一下阿B给我的总结,确实是反复刷了蛮多次的:

Frame4

平时我们的大脑总是被各种信息打扰已经开始慢慢的变得受不了半刻的无聊了,连做饭,上厕所,也一定要找个机会拿起手机填补我们的大脑,但是这样是很不好的,有想过多久没和自己对话过了么,有关心过内心自己吗?

总的来说,正念是给自己一个机会,让自己有空和自己对话,舒缓自己的心情,让自己多和自己和解,有空多多拍拍自己的肩膀,对自己说你已经很棒了!

image-20231230224008005

让自己动起来

游泳

自从我在去年学会游泳之后,现在基本都会每周至少周末去游泳一次,今年游了多少次我就懒得统计了,即使是在这个冬天,我也游泳基本没有偷懒。

游泳的好处我就不说了,最近我一些同事朋友也因为进行户外运动而导致膝盖损伤的案例比较多,而游泳是最好的锻炼方式,在正确的泳姿下运动可以做到锻炼的同时,而不损伤膝盖。

对于游泳的装备来说,有几点需要注意的是:

  1. 眼镜可以的话,最好买防止水汽的,要不然游着游着就会视线模糊,有时候下午的阳光照进游泳池里,光线在水里的折射真的很美;
  2. 泳裤和泳帽不要买太便宜的了,泳帽最好买橡胶的,泳裤买贴身一点的;
  3. 因为游泳池一般是用含氯的消毒剂的,所以最好游完之后用去氯的沐浴露清洗一下。

上面讲的这些迪卡侬都有卖,而且很便宜,买百元左右的价位就足够用了。

健身

其实近一年,我的健身时间很多时候就没怎么把控好,因为我一般是早上训练,一方面我需要在早上9点前赶紧训练完之后去上班,另一方早上真的有点困,不过这也怪我,晚上早点睡就好了 ( ´・ω)

健身其实就和学习是一样的,只要坚持下去就一定会有进步,我过去一年也是按每周4次训练的方式训练,每周胸两次,背两次,深蹲和硬拉各一次。

这里要表扬一下我自己,我的身材真的已经很棒了!

Frame24

因为腰伤的原因,三大项中的深蹲和硬拉有段时间就一直没练,10月重启训练之后一直练的很保守,会在反复确认自己动作没有问题,自己身体能承受的范围内之后才会加重量,所以这两项成绩让大家见笑了。

健身就是这样掺不得一点假,一旦你霸蛮上了你本不该承受的重量,第二天可能就医院见,大家运动时一定要慎重。

卧推的成绩我一直都有跟踪,慢慢的还是有点点涨的,不过我也加的很慢,现在做组可以在77.5kg的水平。

Frame25

瑜伽

有空的话,我也推荐大家练一下瑜伽,这个很重要。因为平时我们久坐人群很容易有各种毛病,例如腰椎和肩颈,适当的花个十几分钟放松一下是很有必要的,我一般练下面的这个视频:

https://www.bilibili.com/video/BV15V411a7cV/

听一下现场的音乐会

这场《宫崎骏&久石让》的演奏会听着还真挺舒服的,让我想起小时候看过的宫崎骏动画。

这场还是蛮惊喜的,小提琴的轻盈,大提琴的悠扬,然后一直是主旋律的钢琴,还有后面总是会带来惊喜的敲击乐,都挺好。

3

我想起来了一个同事在网上的流言,有人问:最近越发不想和人接触,觉得人生无趣,觉得一切都是灰色的,应该怎么排解?

有个同事的回答让我觉得很值得和大家分享:

翻了下网上爬虫的统计(忽略统计时间偏差),豆瓣上评分在8分以上的书有112513 本,8分以上的电影有2094部。

毕加索有一句话:The purpose of art is washing the dust of daily life off our souls.

艺术是当下少有能够跨越时间和空间让一个灵魂与另一个灵魂交流的载体,而经过时间长河沉淀下来的艺术作品,我们有理由相信它来自一个充满色彩的灵魂,那就大可以把时间花在与这些有趣灵魂的交流上,而避免陷在如齿轮般机械运转的生活中自哀。

所以,好好坐下来翻开一本书、看一部电影、听一个歌单,都可以让你觉得世界并不只有灰度值,它还有RGB。如果这样坐下来还是感觉无聊,那就走出去看看几十亿年的地球历史留下来鬼斧神工的自然,抬头看看星空感叹下自己的生命放到宇宙的尺度有多么渺小,自嘲下自己当下的烦恼原来有多微不足道。所以吧,其实你可以做的事情真的不少:)

所以,这场音乐会是真的好听。

出行

2月的香港出行

在香港通关之后的第一个月我就去了香港,大约是2月上旬,其实香港对我来是以前是经常去的,应该没这么陌生,但是封了3年,当时就很想过去看看自己熟悉的地方,是否变得陌生。

这是我当初2月上旬写下的三年没去香港感受到的变化:

  1. 我是在福田口岸通关的,游客通关人数少了很多,开给游客的通关线只有几条,几乎不怎么排队,估计不久游客人数会多起来吧;

    b3bcb695ea01a8f9d0419a9fcac2e914.png

  2. 早上 11 点了,才陆陆续续有店铺开门,还有80%的店没有开门,我记得我以前早上去的时候也不至于这么晚才开门,但是下午在旺角,尖沙咀等地方人还是比较多的;

  3. 在小店买东西的时候,和店员说普通话,他还有些慌乱,不知道该怎么回我,估计是太久没见到大陆游客了,普通话有点生疏了;

  4. 坐地铁可以直接用支付宝了,我全天都是用支付宝坐地铁,但是有些地铁口能用支付宝的闸机比较少;

  5. 如果要使用八达通,需要去香港机器上花15块重新激活一下,并且现在八达通可以转入苹果钱包,可以直接用旅客八达通充值,不需要准备现钞充值了;

  6. 经过国内疫情几年的涨价,感觉香港的物价相比之前几乎没怎么变化,我60 RMB可以在回转寿司饱餐一顿,突然感觉竟然香港吃饭也有了性价比;

  7. 去到港口那里,几乎看不到什么游人,天气即使不怎么好我依然在那里坐了好久好久,吹着丝丝冷风,感觉有一丝丝放松;

    3bf39460195807e90a2a153f7c2e7d36.png

  8. 依然可以找到我喜欢吃的雪糕车,但是这一次吃感觉也没这么好吃,果然还是回忆里的东西比较好;

    Frame9

  9. 这几年国内电车炒的飞起,但是在香港路上电车还是比较少的,依然很多这种老式的出租车,我在想这些车尾气排放怎么样呢?

    Frame10

10月的日本旅行

今年我就只尝试去了日本旅行,虽然时间很赶,但是依然去了蛮多地方,还挺有趣的。我虽然很喜欢看动漫,但是以前从来没去过日本,所以在准备的时候,实际上各种期待,朋友们推荐了各种地方,导致这也想去,那也想去,加上来回路程行程只有7天,但是却规划了四五个地方,导致有点赶,很多地方都没有逛够。

地铁线路是真的复杂

去到日本坐地铁真的很容易迷路,各种线路异常繁杂,并且很多地铁线路都没有连在一起,有时候发现同一个站台进去之后发现不是这条线,要重新刷卡出来再到另一个地方坐车。即使有 Google map 很多次还是搞错,甚至我买了 jrpass 后面也没有怎么用,原因就是太过于麻烦不想每次都花很长时间去找 jr 站台。

img

很清净晚上七八点路上几乎看不到行人

即使在东京也是这样,晚上住在东京的秋叶原附近,晚上7点左右的样子出去觅食,除了商业街外几乎没有行人,写字楼也没有灯,感觉大家下班很早,休息的也很早,很好。

街道真的很干净

这里一整条街都是可以逛的,让我感觉惊讶的是,道路非常干净,在这么多人的情况路上几乎看不到垃圾。

Frame7

即使是心斋桥这条美食街地方也看不到垃圾。Frame8

到处都是扭蛋和自动贩卖机

扭蛋机我也是随便扭了几个,300(15RMB)~500(25RMB)日元一次,这东西随处可见,真的很流行的样子。我也扭了几个,蛮有意思的,但是切勿上头。

Frame26

左右自动贩卖机真的是个好东西,走到哪里都是,并且价格都是一样的,即使再景区,水也是180日元(8RMB)一瓶,而且还是冰的,真的很爽。

面很好吃

虽然被我拍的很没食欲的样子,但是日本的面做的真的有一手的,并且很多面价格都不贵,在800日元(40RMB)左右。

Frame28

富士山很值得一去

虽然这次没有去拍到富士山的雪山,但是它其他景点是真的很棒,我们在富士山那里遇到了几个国人,他们带我们一起去了附近的一个公园,那里很清净,没什么人,大片的草地很平整,我们去的那天还见到了上空有老鹰在盘旋。公园旁边的小河也是很清澈,偶尔有微风吹过,真的很舒服,我们在那里呆了好几个小时。

Frame29

Hep five

这是我第一次坐摩天轮,其实深圳和香港也有一些摩天轮,但是每次路过我都觉得要么太小,要么就太贵,没有去坐,但是这次的Hep five确实是很值。首先是价格只要600日元(30RMB),再来就是它真的很高,有100多米,并且当时我去的时候是下午太阳下山的时候,摩天轮下面就是地铁线路,映衬这个夕阳

Frame30

明日香竟然是个地名

坐地铁无意中发现的,原来看了这么多年的 EVA 里面的明日香是个地名:

Frame31

总结

好了,到这里,这一篇差不多该结束了,感谢你看完了我絮絮叨叨写了这么多,明年总结再见~

扫码_搜索联合传播样式-白色版 1

2023年总结:保持心情愉悦&积极向上最先出现在luozhiyun`s Blog

🔲 ⭐

高能量姿势:让自己心情变好点

写这篇文章主要是最近听了一个播客《魅力玄学大讨论:到底是什么样的人,在受欢迎啊! https://www.xiaoyuzhoufm.com/episode/64ad7cb4b21ecc647deed28f》里面提到一个《高能量姿势》这本书,于是去看了作者的 TED。

Amy Cuddy 早在 2012 就在 TED 上面分享了自己的研究发现《Your body language may shape who you are https://www.ted.com/talks/amy_cuddy_your_body_language_may_shape_who_you_are》,看了之后确实感觉有种被 spark 到的感觉于是想把这种提高自己身体能量的方式分享给大家,并且结合自身体验,给出一些我自己觉得不错的提升能量的其他方式。

其实保持高能量状态是一件很好的事情,可以让自己生活精力更充沛一些,生活也更积极一些。感觉自己有能量的人往往会比较果断,自信,乐观,并且确切的认为机会永远站在他们这边。

高能量姿势

我们都知道,我们平时在交流的时候,除了用语言,也会使用肢体去表达我们的想法,并且通常肢体语言能传递很多信息,比如情绪,想法,判断等。甚至在线上我们看不到彼此的时候,妥善的使用表情能够让我们更好的表达自己,从而更顺畅的进行交流。

比如当我们异常激动或兴奋的时候通常会展开自己的身体,当我们感觉弱小无助的时候通常会蜷缩自己的身体。

Frame 1

所以同样的,我们的想法会影响我们的行为,行为也是会反过来影响我们的想法的。当你假装自己很强大很有能量的时候,就会真的觉得自己很强大。

Amy Cuddy 选了几种姿势来进行实验,左边姿势是舒展开来,开放性的姿势,我们称之为高能量姿势;右边的是蜷缩起来、封闭式的姿势,我们称之为低能量姿势。他们让两组人分别做这样的姿势两分钟进行实验。

img

实验发现摆出高能量姿势实验组会有较高的睾酮水平和较低的皮质醇,这些激素的改变会使你更能承受压力,更加自信,更加充满力量。所以从实验看来姿势确实可以反过来影响我们自己。

Frame 2

那么知道姿势可以改变我们的想法,其实就可以加以运用,比如在紧张的面试之前可以舒展两分钟的姿势,让自己面试更加自信;比如演讲前,我们也可以运用高能量姿势提升自己的士气。

其他方式提升能量

健身

健身实际上会让自己体型更加好看,自己体态显得更加年轻。举一个很简单的例子,每次走路的时候想要低头,发现会看到自己的大胸肌顿时就会感觉自己真屌,立马不自觉的昂首挺胸。然后昂首挺胸这个姿势实际上也是个高能量姿势,会不知不觉提高自己能量。

再来就是健身实际上也是一种健康的生活方式,会让自己更有力量感更有精神一些。开始健身之后你会感觉到自己的体型不断变好,由体型的改变甚至可以引起向外散发的气质上的改变。

健身不仅仅指的是去健身房举铁才算是,像游泳、跑步、爬山、瑜伽都算,健身其实不难,难的是坚持,比如一周2~3次就可以很好的达到拥有健康身体的目的。

正念冥想

现代生活的信息密度实在是太大了,短视频、信息流就像漩涡一样,很多人的注意力经常被不停的卷进去,根本没有时间去思考自己的状态,没有时间静下来,脑子里总是有无数种想法,无法活在当下的感觉中。

正念冥想其实就是让你自己处于一个第三者的状态观察自己,给自己一个放下脑中想法的时机,让自己感受身体的状态,感受脑中浮现的想法,身体的感触,让自己慢慢活在当下。

自我暗示

我们潜意识会受自我的影响以及他人的影响,其实自我暗示就是一种自我改变潜意识的方式,潜意识可能在不知不觉的影响到我们对世界的认知,或好或坏。

开发潜意识最重要的关键是——重复,也就是将我们想输入的信念或行为习惯,重复不断地通过我们的感官输入大脑,一旦某一信息输入潜意识后,它会直接影响我们的行为。所以我们可以通过对自己的积极暗示,提升自己的能量,比如可以早上起来刷牙的时候,可以对自己说:"每一天,我生活的各个方面都变得越来越好",这是一个自我暗示练习,将生活的各个方面都包括了,通过重复,就会深深地印在潜意识中。

Reference

https://www.ted.com/talks/amy_cuddy_your_body_language_may_shape_who_you_are

https://www.xiaoyuzhoufm.com/episode/64ad7cb4b21ecc647deed28f

扫码_搜索联合传播样式-白色版 1

高能量姿势:让自己心情变好点最先出现在luozhiyun`s Blog

🔲 ⭐

C++ 中让人头晕的const & constexpr

在使用 C++ const 的时候,看到 const 这些用法脑袋都是晕的,如 const int*const int * constint const *。并且在 C++ 11加入了 constexpr 之后也不知道它和 const 有什么区别,这篇文章主要就是整理一下这方面的知识点。

const

const 一般的用法就是修饰变量、引用、指针,修饰之后它们就变成了常量,需要注意的是 const 并未区分出编译期常量和运行期常量,并且 const 只保证了运行时不直接被修改。

一般的情况,const 也就简单这么用一下,const 放在左边,表示常量:

const int x = 100; //常量
const int& rx = x; //常量引用
const int* px = &x;//常量指针

给变量加上 const 之后变量”就成了“常量”,只能读、禁止写,编译器会帮你检查出所有对它的写操作,发出警告,在编译阶段防止有意或者无意的修改。这样一来,const 常量用起来就相对安全一点。所以在设计函数的时候,将参数用 const 修饰的话,一个是可以保证效率,另一个是保证安全。

除此之外,const 还能声明在成员函数上,const 被放在了函数的后面,表示这个函数是一个“常量”,函数的执行过程是 const 的,不会修改对象的状态(即成员变量),比如:

class DemoClass final
{
private:
    const long  MAX_SIZE = 256;    // const成员变量
    int         m_value;           // 成员变量
public:
    int get_value() const        // const成员函数
    {
        // error: assignment of member ‘DemoClass::m_value’ 
        // in read-only object
        m_value = 100;
        return m_value;
    }
};

const 用于指针还有其他情况,const 放在声明的最左边,表示指向常量的指针( pointer to const int),指针指向的是一个“只读变量”,不允许修改:

   int x = 100;
   const int* px = &x; 
   *px = 102; // error: assignment of read-only

const 放在 “*” 的右边,表示指针是常量(const pointer to int),指针不能被修改,而指向的变量可以被修改:

   int x = 100;
   int b = 150;
   int* const px = &x; 
   *px = 102; // success
   px = &b; // error: assignment of read-only location

还有一种就是 const 放在 “*” 两边,表示指针和指向常量的都是指针(const pointer to const int),表示指针和变量都不能修改,具体的大家可以去试试。需要注意的是下面几种情况是相等的:

  • const int * == int const *
  • const int * const == int const * const

其实这也引出了 David Anderson 的这篇文章《 The “Clockwise/Spiral Rule”》,这篇文章主要就是讲解如何用顺时针的方式去理解一些 C 语言的复杂的声明。

比如对于下面的这个声明:

       +-------+
       | +-+   |
       | ^ |   |
  char *str[10];
   ^   ^   |   |
   |   +---+   |
   +-----------+
  • 首先想到的是 str 是什么?我们以 str 为顺时针转动,先遇到了 [ ,所以表示 str 是个数组,所以 str 是一个容量为 10 的数组;
  • 然后我们再顺时针移动,下一个遇到的是 * ,那么表示这是一个指针,那么 str 是一个容量为 10 的数组指针;
  • 我们继续顺时针转动,发现遇到了行结束符 ;,跳过它继续转动,发现遇到了 char,那么表示 str 是一个 char 类型,容量为 10 的数组指针。

所以相对的,我们用这个方法去理解 const 用于指针的情况,我借用一下 https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const 这里面一位老哥画的图,其实就很好理解了:

pointer to int

const pointer to int const

pointer to int const

pointer to const int

const pointer to int

constexpr

constexpr 它是在 C++ 11 被引进的,它的字面意思是 constant expression,常量表达式。它可以作用在变量和函数上。一个 constexpr 变量是一个编译时完全确定的常数。一个 constexpr 函数至少对于某一组实参可以在编译期间产生一个编译期常数。

需要注意的是 const 并未区分出编译期常量和运行期常量,并且 const 只保证了运行时不直接被修改,而 constexpr 是限定在了编译期常量。所以在 constexpr 出来之后, const 的职责被拆分出来一部分,只作可读语义的保证,而常量语义交给了 constexpr 负责。

在 C++11 以后,建议凡是常量语义的场景都使用 constexpr,并且由于它是一个编译期常数,所以它甚至可以用在模板上,例如:

template<int N> class C{};

constexpr int FivePlus(int x) {
  return 5 + x;
}

void f(const int x) {
  C<x> c1;            // Error: x is not compile-time evaluable.
  C<FivePlus(6)> c2;  // OK
} 

关于 const 和 constexpr 的提问在 stackoverflowzhihu 这里讨论了很多,更详细的可以自己点进去看一下。

C++17 & if constexpr

在 C++17 的时候多了 if constexpr 这样的语法,使得模板编程的可读性更好。

我们先看个例子,在 C++11/14 的时候,我们要使用前面讲到的 enable_if 模板的话,通常要实现两个 close_enough 模板:

template <class T> constexpr T absolute(T arg) {
   return arg < 0 ? -arg : arg;
}

template <class T> 
constexpr enable_if_t<is_floating_point<T>::value, bool> 
close_enough(T a, T b) {
   return absolute(a - b) < static_cast<T>(0.000001);
}
template <class T>
constexpr enable_if_t<!is_floating_point<T>::value, bool> 
close_enough(T a, T b) {
   return a == b;
}

但是在 C++17 中配合 if constexpr 这样的语法可以简化成一个 close_enough 模板,并且将常量抽离出来变成 constexpr 变量:

template <class T> constexpr T absolute(T arg) {
   return arg < 0 ? -arg : arg;
}

template <class T>
constexpr auto precision_threshold = T(0.000001);

template <class T> constexpr bool close_enough(T a, T b) {
   if constexpr (is_floating_point_v<T>)  
      return absolute(a - b) < precision_threshold<T>;
   else
      return a == b;
}

使用 if constexpr 编译器会在编译的时候计算这个分支是否符合条件,如果不符合条件会做优化丢弃掉这个分支。

Reference

https://time.geekbang.org/column/article/238486

https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const

https://c-faq.com/decl/spiral.anderson.html

https://stackoverflow.com/questions/14116003/whats-the-difference-between-constexpr-and-const

https://www.zhihu.com/question/35614219

扫码_搜索联合传播样式-白色版 1

C++ 中让人头晕的const & constexpr最先出现在luozhiyun`s Blog

❌