V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  kuanat  ›  全部回复第 7 页 / 共 12 页
回复总数  233
1  2  3  4  5  6  7  8  9  10 ... 12  
219 天前
回复了 vx7298 创建的主题 Ubuntu ubuntu 一如既往的优秀!
@ityspace #47
@424778940 #65
@Jirajine #51

作为发行版来说,我认为现阶段 NixOS 是不够严肃的,定位大致可以接近于弱化版的 Arch 。作为语言的 Nix 和作为包管理的 Nixpkgs 也不能直接拿来对比的,但是这三个经常被混在一起。比如 NixOS 官网提到的三个特性关键词 Reproducible/Declarative/Reliable 实际上分别是描述 Build ( Nixpkgs )/Language ( Nix )/OS ( NixOS )的。(另外官方的正式名称 Nix 既是语言,又是包管理器,Nixpkgs 指的是软件仓库)

按时间来说,我印象是先有的 Nixpkgs 的想法,为此设计了 Nix 语言,最后才有的 NixOS 。早期(至少十年前了) Nixpkgs/Nix 的文档实在是太简陋了,大部分时间我都是看着代码去猜的……当然现在易用性好了很多,只是我个人的评价是,Nix 系的设计目标是有可借鉴之处的,但是实现方式和哲学我不是很认可。

当年我被 Nixpkgs 吸引的原因是它希望用新的方式(在当时是很超前的思路)*同时*解决包管理器常年存在的几个问题:一是多版本共存,二是依赖解析,在此基础上顺便提供两个新特性:支持原子操作和回滚且无需依赖特定文件系统,再就是 Reproducible 构建。实现方式上,用的是现在被很多包管理借鉴的叫做 Content-addressable storage 的存储后端,以及类似沙盒化的构建过程(主要是记录依赖树而非权限限制)。

换个简单的说法,这个包管理的思路就是:记录依赖树上每个包的版本,同名包不同的版本会各存一份,相同版本也只存一份,而目标应用的执行环境(沙盒)中只有用得到的特定版本的依赖。这是个很理想化的设计,也存在很现实的问题。根据 Debian 过去十多年的构建经验,符合 Reproducible 构建标准(二进制一致)的包的占比一直在 85% 左右(构建这个行为会引入包依赖之外的变量)。Nixpkgs 意义上的 Reproducible 更接近于容器镜像可移植 Portable 的概念,即每次构建可能不同,但可以相互替换。

对于开发者来说,一般不关心打包的事情,但多数都会提供构建脚本。对于打包者来说,针对特定系统的打包希望尽量标准化,配置尽可能简单,原作者的构建脚本能拿过来用最好。对于包管理器来说,依赖最好是清楚写明的而不要是推断的。在 Nixpkgs 的设计者来说,当时没有合适的工具,所以想要创建一门新的语言,功能需求至少有几个:有脚本的灵活,声明式的写法以及模板化的生成能力。

至少在 Nixpkgs 层面,我是很欣赏设计思路的。到了 Nix 这门语言的层面,我的能力不足以做出准确的论断。仅从用户的角度上说,学习成本和易用性门槛都是相对比较高的。我个人认为 Nixpkgs 可以和 Gentoo portage 相媲美,都可以作为非常好的构建平台。

当包管理器想要往发行版方向发展时,限制就显现出来了。我一直说 RedHat/Debian/Suse 系是相对严肃的发行版,主要是因为它们都有足够的维护者。Nix 目前官方仓库的维护水平差距还是比较大的,而且可以遇见由于用户基数和门槛的限制,这个改善过程会很艰难。

另一方面我认为 NixOS 在发行版或者操作系统的理解定位上有点走偏了,就好比 Nixpkgs 是很好的锤子,但是 NixOS 太想把发行版塑造成钉子的形状。而且无论是 Windows/Linux/macOS 经过这么多年的发展,比较明显的共识是操作系统应该追求 Immutable 而不是 Reproducible 。这方面 NixOS 的实现方式永远比不上基于文件系统( layered )的效果,所以 NixOS 只能宣传自己 Reliable 却无法说自己是 Immutable 。

再就是用户数据理应和系统分离,应用程序理应和配置分离。NixOS 推崇的把用户、配置纳入进来的思想在我看来耦合有点过深了。不是说这样做不好,只是不太符合我个人的 Linux 哲学认知。
219 天前
回复了 kkocdko 创建的主题 Linux Linux 笔电的所谓省电技巧
@Donduck #52

这个本来是想放到第三层里说的。有能力通过 ec/smu/acpi 方式去调节的话,确实比系统自带的方式好。因为是硬件平台相关的,所以没有什么通用性。(我感觉 6000 系之后离电性能限制就很小了,不清楚是不是个例)

追根溯源的话,Intel/AMD 的市场竞争导致双方都在比拼频率,毕竟评测的时候会好看一些。原本默频应该是芯片工艺的甜点,睿频功耗高但是能换来一定提升。现在标定的默频都已经是睿频水平了,睿频实际上在 HWP 调度下变成了出厂灰烬超频。结果就是续航不理想,然后 AMD 那边 BIOS 还有平台配套开发不太给力,所以简单一刀切限制了离电性能。

对于普通用户来说,在没有这样的应用的情况下,想要调节就很困难了。多数都是通过逆向厂家给的控制程序,判断是 WMI 还是 ACPI 方式,又或者是 EC/SMU 控制,然后在 Linux 写程序还原这个操作。
219 天前
回复了 kkocdko 创建的主题 Linux Linux 笔电的所谓省电技巧
这个帖子竟然跑题了……我补充一点可能用得上的吧。

关于调度:

如果要详细说 CPU 调度,大约可以分三个部分:

- 系统层面最上层是一个“电源管理”机制,用途是让用户告诉操作系统,我的需求是性能全开,还是静音保证续航。多数实现就是性能/平衡/续航这样的选项。

- 第二层是 scaling driver ,这是系统内核向 CPU 发送调度指令的中介。用户层面的电源管理应用,将用户的意图转化为具体的调度参数。

在早期 Intel/AMD 都没有特别的调度机制的时候,这个调度参数其实是 ACPI 标准中的 P-State ,P 状态的定义就是多少电压对应多少运行频率。这个时代,调度还是软件占主导的。随着现代 CPU 越来越复杂,基于 ACPI P-state 调度不够用了。所以现在 intel_pstate/amd_pstate_epp 也变成了中介,不再直接控制频率,而是将意图给予 CPU 由 CPU 自身的硬件 HWP 来确定实际的调度。

- 第三层就是 CPU 和主板 BIOS 了。CPU 能否真正完成调度还要看 BIOS 限制,比如 PL1/PL2 这种长短功率限制、睿频开关等等。笔记本厂家通过 BIOS 完成所谓的“调教”。

部分开发能力较强的厂家,还会提供用户空间的控制程序,在 BIOS 之外完成相关参数设定。相当于手动覆盖第一层系统的电源管理机制,只是我没见哪家厂商给 Linux 做相关的适配,控制程序多数只能在 Windows 用。



综合上面所说的,对于用户来说,第二层是一定要用 Intel/AMD 自家驱动的。至于用户空间的第一层调度,我的建议是用系统自带的就好了,折腾不出什么花样来。我自己的话还是有能力利用一下第三层的,比如把 Windows 的控制程序移植到 Linux ,这个事可以有机会再说。


关于 TLP:

我的建议依旧是不要折腾,TLP 代码就在那里躺着,实现方式就是脚本。看过了之后再下判断也不迟。
221 天前
回复了 KinsleyNg 创建的主题 Linux 这个 Linux 的 chrome 硬解就这么难处理吗
根据 Pop!_OS 加上 nvtop 的提示,我估计是内核版本低了,驱动比较老,没有暴露足够的 sysfs 抽象。780M 是 RDNA3 架构,大概要 6.4 之后的版本才能有比较完整的驱动支持。Pop 是 Ubuntu 22.04 LTS 的衍生版,内核可能还是 5.X ,我不确定官方仓库是不是有足够新的内核。首先要保证内核版本够高,能正确加载 amdgpu 驱动。

如果你说的 chrome 是官方那个 deb 版本,硬件加速是通过 vaapi 实现的,这个版本编译的时候是带了 vaapi 支持和相应解码器的。(某些发行版仓库里 chromium 编译时未开启 vaapi ,像红帽系的发行版 fedora 由于版权问题是缺少一些解码器)

然后为了支持 vaapi ,需要 libva-mesa 驱动。libva 还有个使用 vdpau 后端的驱动,主要是供老一点的型号用的,vdpau 在新架构上基本被 libva-mesa 替代了。

再之后由于 mesa 在 amd 设备上只支持 Xorg/XWayland ,所以要保证 chrome 运行在非 wayland 模式下。如果你没手动指定的话,大概率 session 就是 Xorg 的。

最后就是 chrome 播放视频验证的时候要确认编码格式。说这么多其实我猜测比较可能的原因就是驱动版本和 libva-mesa 。安装好了应该就正常了,还不行你可以再来反馈或者去报 bug 。
续航这个事情,用户能做的事情不太多。不想折腾的话就是买有驱动支持的型号,然后装内核版本尽量高的发行版。


这个事情误区太多,我就多说一些。

首先功耗是动态的,如果拿手机来类比,一般看续航都是所谓的亮屏,然后才是待机。换句话说,高负载一般是用户控制不了的,能优化的其实是低负载的部分。其次如果考虑功耗构成的话,有负载的时候,显示屏可能在 3~5 瓦左右(根据分辨率和亮度会变化),SoC 等等根据平台可能有 15/30/45 瓦这样。低负载的情况下,显示屏还是 3~5 瓦,但是 SoC 可能会降到 1~2 瓦的水平。

所以很容易得到两个结论,工作负载基本只能看耗电大户 SoC 的工艺水平,也就是能耗比,越先进的平台一般越省电。待机功耗已经非常低了,即便把 CPU 降频降压等等,对于全天使用这种混合工况,能够改善的空间也很有限。


那现在硬件厂商、操作系统在不影响用户体验(性能释放)的前提下,还有哪些改善续航的手段?

第一个思路是改变高、低负载工况的比例。这是基于 CPU 的特性功耗范围非常大,短时间高性能快速完成计算任务然后待机,要比长时间中低性能的模式平均功耗更低。以前 CPU 多数运行在甜点频率,然后睿频能够起到平衡性能和功耗的作用,现在越来越出厂灰烬,所以想要长续航还是尽量选为移动平台设计的处理器版本。

第二个思路是没有低负载也要创造低负载,用不到的设备就让它待机。近几年的硬件几乎都支持运行时状态调节,大到 CPU 小到 WiFi 网卡 SSD 硬盘,都可以工作在高性能/节能/待机等模式下,整体都符合 ACPI 规范。如果所有硬件都支持,那么待机功耗能够降低到非常可观的水平。如果硬件平台比较老,或者某些设备只有 Windows 驱动,就会造成无法进入低功耗状态,因而导致续航功耗降不下来。

顺着第二个思路继续延伸,待机的意义其实可以放得更宽。想象一下浏览网页的场景,可能用户只会断续滚动一下以页面,其他时候都是在阅读。这个断续无操作的场景,就是可以激进待机的时机。于是在多方努力下,除了正常工作状态、待机和休眠,又多了一个叫 ModernStandby/s0ix/s2idle (分别是 Windows/Intel/Linux 的叫法)的状态,这个状态功耗接近待机,而唤醒时间非常短。

不过这个特性属于不能用短板那种,一旦某个设备无法进入 idle 状态很可能会把整个系统拉下水。之前 Intel 搞 EVO 认证其实就是这个用意,强制厂家筛选硬件保证这个激进待机能正常运作。

显示屏是这个机制非常好的受益者。高分辨率高刷新率显示屏其实是耗电大户,因为它同时会使 CPU 显示屏工作在有负载状态,还使得二者之间的链路( pcie/hdmi )都更加耗电。上述的待机场景,显示内容是不变的,如果显示屏能缓存输出信号自行显示,即可让 CPU 显示屏和链路都进入低功耗状态,这个技术叫 PSR 面板自刷新。配合 s2idle 机制,在长时间使用时可以将平均功耗从 3~5 瓦的水平降低一半。


之所以在上面说软件能做的事情不多,是因为新平台、新技术和新内核的加持下,默认就很好了。我有一台 Intel 11 代的 16 英寸笔记本,型号就不提了,电池容量大概 70Wh 。用 Linux 没做任何设置,满足 8 小时写代码开发工况是很容易的。满电待机(不是 s3 而是 s2idle )能有 250 小时,也就是说不含显示的功耗能低到 0.3W 左右。

如果真的要折腾一下,Nvidia 独显用户可以考虑 Bumblebee 做个切换。驱动尽量新一点,这样内核可以在 idle 状态下同时让显卡也降低功耗。CPU 调度方面 intel_pstate/amd-pstate 就很好,比绝大多数主动调度要靠谱,可以根据情况手动切换是否开启睿频。至于 TLP 在较新的硬件平台上已经没有什么作用了,针对老硬件不支持运行时电源状态管理的,最好的办法是 udev 规则手动加载卸载。缺乏驱动支持的设备,比如指纹识别什么的也是同理。重点就是关注有没有什么硬件或者 usb 设备影响了系统进入待机状态。

最后说一下 UPower ,它几乎运行在所有发行版上。原本的用途并不是省电,只是恰好有控制硬件中断和延迟的功能。这样即使用户空间的应用程序有不正常的硬件 polling 行为,也不会影响到硬件层面的 idle 和唤醒。(安卓在这个思路上继续延伸,wakelock 增加了对齐机制。)
TLDR: Hyprland 总体来说很不错,日常可用稳定性也可以,开箱即用的水平中游偏上,比不上 KDE/Gnome ,但好于其他同类 wm 。

目前 Wayland compositor 的实现主要有三个:Mutter(Gnome)/Kwin(KDE)/wlroots(sway),前两个与 DE 绑定,其他桌面实现包括 Hyprland 都是基于 wlroots 的。区别重点有两方面,一是对于协议的支持,协议指的是各种功能规范,比如输入法、锁屏等等,另一方面是渲染后端支持。

渲染后端指的是 framebuffer 的抽象,Intel/AMD 显卡用的是 GBM ,而 Nvidia 用的是 EGLStreams 。Kwin 已经官宣放弃支持 EGLStreams ,Mutter 支持比较好,而 wlroots 从一开始就不支持(有 fork )。Nvidia 的闭源驱动从 495.44 之后提供了 GBM 的实现,所以新一点的显卡,是可以正常跑 Wayland 的,但是有不少兼容性问题,或者用开源驱动,但就是功能和性能不太行。

窗口合成器一般分 stacking/tiling/scrolling 几类,帖子主题是 Hyprland 那就只聊 tiling 类型的。现在基于 wlroots 的主流实现就是 Sway/Hyprland/dwm/river 几个。基于 wlroots 也有可能是基于某个版本的 fork 实现,所以支持的渲染后端和协议也不同,比如 Hyprland 就早于 Sway 支持 input_method_v2 ,但由于 Hyprland 通过 GLES 做的特效,所以尽管上游 wlroots 已经支持 vulkan 但 Hyprland 就无法支持。

dwm/river 我没怎么用过,就不评价了。如果要在 Hyprland/Sway 里面选,除了上面说的 vulkan 支持,就看你的使用习惯是手动还是自动布局。Sway 是纯手动的,Hyprland 支持 MasterStack 模式的自动布局。Sway 开放了 IPC 实际是可以很简单实现自动布局的,不仅可以用 Python 之类的脚本来写,也有其他语言的 binding 。Sway 自由度更大,加上是 wlroots 的官方开发,所以稳定性好很多。



PS 随便多说点,给想尝试 Sway/Hyprland 的做个参考。

由于 Sway/Hyprland 只是个 WM ,所以真要日常使用需要额外配置很多东西,这里列举几个影响使用体验的因素。

第一个就是常说的分数缩放,然后又要分 Wayland 原生应用和 XWayland 应用。(后者只有 KDE 做了额外的支持,其他几家不做的原因主要是理念,wlroots 这边的意思是,不该我管的事情我不管。) XWayland 应用一定会模糊,但是这个影响已经很小了,现在 Chrome 系和 Electron 都已经有了原生实现,可以通过命令行参数切换到原生模式。

相关的协议 wp-fractional-scale-v1 大概是去年合并的,在此之前的渲染逻辑是 200% 渲染之后 downsample 。新协议变成了客户端(应用程序)可以获知缩放参数,然后自己完成界面逻辑。只是这个新协议要 GTK4/QT6 的支持,不过在不支持新协议的应用上( GTK3/QT5 )还是会回到之前的模式上。无论哪种模式都不会模糊,只是新协议不需要 200% 渲染这种低效的操作了。

第二个多屏幕支持,我个人认为非常好了,不同显示设备可以支持不同 DPI 。由于 tiling 模式是不存在某个状态一个窗口横跨两个显示设备,所以处理起来简单很多。

这里 Sway 有个优于 Hyprland 的功能叫 multiseat ,可以按照 seat 为输入输出设备进行分组,每个 seat 有独立的输入焦点。这个功能对于多输入设备(触摸板/鼠标/触摸屏)和多屏非常有意义,比如多焦点的情况下,可以在副屏上触摸操作,而不影响主屏。

第三个是中文用户关心的输入法。由于 wayland 输入法相关的协议众多,基本不建议用 fcitx5 之外的任何输入法框架,因为 fcitx5 已经实现了所有能支持的一切。Sway 1.9 也合并了 input_method_v2 ,所以和 Hyprland 没区别了。应用程序侧 input_method_v2 主要是影响某些终端 vte 显示候选框的问题,chrome 系支持了 wayland-ime 和 gtk4 也可正常使用了。

第四个是状态栏,主要的实现是 waybar/yambar/eww 几个。最大的麻烦是 tray 支持,即在状态栏显示客户端应用自定义的图标,并支持交互功能,毕竟不是 Gnome/KDE 不会有应用专门去做相关支持的。waybar 稍微好一丢丢,因为它本身是个 gtk 应用,eww 算是套壳,yambar 需要自己想办法。

小问题是状态栏的工作逻辑,我看到的除了 yambar 大部分功能实现都是基于 polling 做状态轮训,这其实是个比较低效的做法,比如显示笔记本当前电池电量,正确做法其实是通过 udev 规则触发显示更新。再比如不使用 tray 的情况下,要显示输入法状态轮训就不合适,因为这个状态切换需要接近实时,然后绝大部分时间状态是不变的。合理做法是用 fcitx5 lua 功能监听状态变化事件,然后主动触发显示更新。

第五个是锁屏。之所以说这个是因为锁屏可选的实现很少,目前一定要正确实现 ext-session-lock-v1 才可以。原本 Linux 图形界面的通常逻辑是在 tty 之上的,比如 Gnome 登录界面( gdm )一般在 tty1 上,登录验证完成后,在 tty2 上开一个当前 session 的界面,锁定后又会回到 tty1 上。目前应该是只有 Gnome 把锁屏做到了 wm 上,其他都是用一个单独的程序当作锁屏。这就是前面那个协议的意义,这个锁屏程序如果 crash 掉了,正确实现协议可以保证不会意外解锁。

第六个是截图录屏,放到前两年这还算个事,调用 wayland 实现的截图录屏软件就都没什么问题。有问题的是类似 XX 会议这种,如果自身是 XWayland 实现,就没法共享桌面,有极其复杂的曲线救国的方式但是不推荐。如果是基于浏览器或者 electron 的实现,就可以正常录屏,因为相关调用走的是 wayland 协议。

剩下的放到一起说,主要是 systemd 集成和各种权限问题。我估计应该没有人刻意不在桌面环境用 systemd ,大部分人都会主流 DE 选一个然后再另外安装 Sway/Hyprland ,少部分 Arch/Gentoo 用户会直接上。systemd 能简化很多事情,但不熟悉的话很多细节问题就难以解决。

举个例子来说,因为不是 DE 而是 WM ,就连调节屏幕亮度这样的功能也要自己写脚本实现。但是调节亮度需要操作对应设备 sysfs 文件,那就有几个思路:要么给当前用户 sudo 命令免密码的授权,要么 udev 规则给当前用户显示设备权限,要么写个程序赋予 SUID ,或者是调用 systemd-logind DBus 接口。从安全的角度来看,当然是最后一个方法最合理,但是绝大多数人应该都不了解。

再比如 systemd-logind 默认对于电源按键的响应是关机,希望调整为待机,如果是 Gnome 这种,可以在系统设置里去调整。按照一般思路,用户会去修改 logind 的配置。但按照 systemd 的设计和各个主流 DE 的实现,符合逻辑的做法是通过 systemd-inhibit 获取 handle-power-key 的锁,这样 systemd 会忽略对 XF86PowerOff 按键的响应,之后 WM 可以自行绑定相关按键来执行想要的功能。

如果是从其他 DE 迁移过来,之前依赖特定桌面的功能,比如 keyring 什么的,就需要调用对应的 polkit agent 来获取授权。其实 DE 做的有关用户体验的事情非常非常多,想要全都迁移到 WM 层面就需要非常多的工作。我再举个例子,Gnome/gdm 的登录是可以支持同时对密码和指纹进行验证的,这里同时的意思是按输入键就验证密码,直接刷指纹也可以。我原本以为这是个很简单的配置问题,研究之后才发现,密码和指纹校验本身确实都是 PAM 机制来做,但仅仅依靠 PAM 机制是做不到同时检测的。PAM 的设计就是线性序列化的,要么先指纹,失败了再验证密码,要么反过来。想要做类似的功能就要写一个并行验证的逻辑,然后这个模块又必须正确实现锁屏协议,还要负责处理 session 相关的管理,这就不是个人用户能手搓的了。

做个简单总结,如果你对系统足够了解,开发能力足够强,Sway/Hyprland 这种 WM 经过自定义能获得比 Gnome/KDE 更好的体验,只是需要花费非常大的精力。
233 天前
回复了 frostming 创建的主题 Python 有一个包管理器叫 PDM,已经四年多了
PDM 真的很棒,我上次看到作者的回复,想赞美一下 PDM 结果打成 PNPM 了……

对于非专业人士和初学者,我还是会推荐 PDM 的。原因一是基于 Python 生态及现有标准,又比 pip 方便友好,二是成熟项目基本不会踩坑了。


从趋势来看,随着 AI 的兴起,这些年“包管理”这个概念慢慢在向“工程管理”方向转变。生态层面上,就看 uv 能不能统一 Python 工具链了,在这个背景下,独立的包管理工具可能会慢慢淡出视线。

另外 conda 的思路也不一样,我个人认为它更接近于 docker build 这种重建。

就同楼主说的一样,专业人员肯定是要考虑实际需求做选择的。
235 天前
回复了 zachary99 创建的主题 Linux 哪个版本 Linux 对多屏显示友好
第一个大概率不是软件问题。建议详细描述一下笔记本型号、硬件配置和连接显示器的方式等等方便大家给出建议。

我印象中 Intel 过渡到真三显支持应该是 11 代之后的事情。在这之前对于三显的支持是内部一个,外部两个共用一个信号源,就是你所说的外接的两个显示器只能复制模式。Nvidia/Amd 独显也有类似的问题,但是原因不一样,主要取决于拓扑结构。

第二个问题,常见的登录界面( gdm/sddm )的软件逻辑是一个特殊账号的桌面,这个账号有自己的显示器配置。一般你把自己账号里面的显示器配置文件复制到这个特殊账号里面覆盖掉就好了。
237 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
@tuoov #25

楼上提到的 https://research.swtch.com/mm 就可以。

另外我印象里内存模型这个概念出现得比较晚,Java 应该是第一个把内存模型理论化的,在这之前的比如 C 都是先有语言后建模,之后的新语言比如 Golang 才是先设计内存模型,后规范语言。

Java 后来发现原本设计的内存模型有问题,所以有了 JSR 133 来做修正。可以通过 http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html 向外发散阅读。
238 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
@rockyliang #20

这部分我也不太清楚,猜测影响的因素会有很多,编译器实现方式影响可能大一些。

如果仅从语言层面上说,Golang 的内存模型里面随机结果的种类会少一点,官方编译器倾向于减少这些不确定性。C 的话编译器很多,基本很难预期 UB 的结果。
238 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
@doraemonki #16

“[2]一定执行了,那么可以推出[1]也执行了“ => 这句话不正确。

参考"happens-before 和指令执行的先后顺序无关,不是时间上的先后概念"这一部分。这句话解释起来就比较麻烦了,建议去看内存模型相关的文档,一般都是有示例来辅助理解的。happens-before 和时间上执行先后顺序无关。

[1]<=[2] 和 [3]<=[4] 这两个条件不能通过传递性获得任何 [1] 与 [4] 的关系。具体到这个例子,时间顺序上按 [2],[3],[4],[1] 的先后执行,恰好编译器使得 [2] 的结果对 [3]可见,这样是不违反规范的。

参考“编译器的工作从来都不是对指令执行流程进行排序,只要能保证可见性,指令顺序是不影响预期结果的。”

尽管 [1]<=[2] 但 [1] 和 [2] 的时间先后对二者都没有影响,[3] 和 [4] 相互也没有影响,他们都没有操作相同的内存变量。换句话说,由代码书写先后推导出的 [1]<=[2] 和 [3]<=[4] 是没有用的额外条件。这四条指令可以按任意时间顺序执行,都满足规范。
238 天前
回复了 rockyliang 创建的主题 Go 编程语言 关于 golang 官网一段代码的疑惑
我来尝试用最直白的方式解释一下吧,

原文 The Go Memory Model 是严谨准确的,只是涉及到专业术语,没有相应的理论背景很难理解。

第一个容易误解的概念是 guaranteed 。编程语言定义规范( spec ),编译器负责在目标平台上完成实现。保证( guaranteed )的意思就是,在满足特定条件的情况下,无论编译器如何完成,都要确保最终的结果符预期。

看上去和什么都没说一样对吧?结合相反的概念就明白了,这个相反的概念叫 undefined 。也就是 C 时代大名鼎鼎的 undefined behavior 那个 undefined 。因为编程语言没明确要求,编译器想怎么干就怎么干,也可以什么都不干。

所以 guaranteed/undefined 的实质区别就是,前者以确定的方式干了点什么,从而保证了结果和预期的一致性。至于具体干了什么后面再说。

接下来的概念叫内存模型( memory model ),这是一组对规范( spec )的抽象而形成的规则。毕竟代码写起来可以五花八门,但内在逻辑是相似且有限的。但是如何用形式化( formal )的方式来精准定义这些逻辑呢?

从最简单的例子开始,假如只有单线程,那么执行顺序就是代码的书写顺序。如果是多线程呢?(这里还是取最简单的情形来简化分析)

思考一下就会发现,不确定性的来源主要是两类:一类是对于同一(共享)变量(内存)的读/写并行操作,另一类是写/写操作。这些的潜在冲突被叫做竞态( race )。

设想最简单的读写冲突场景,读和写操作分别位于两个独立的并行执行序列中。如果预期的结果是先读后写,那什么都不用做,这两个指令不会相互影响;但是反过来,预期先写后读,且要保证( guaranteed )读到写的结果,就需要编译器做点什么了。

这里“读到写的结果”有个专门的名词叫可见性( visibility ),编译器具体怎么做不重要,重要的是编译器做了一件事,在写操作完成读操作发生之前,使得写操作的结果对于读操作可见。(要么共享一份副本,要么两份独立的副本但同步成为相同的结果)

最后的任务就是,编程语言在规范( spec )中定义,在什么情况下编译器需要保证可见性( visibility )。即判断开发者按照直觉写出的代码,它的预期执行逻辑应该是什么样的。

这背后的代码逻辑关系被定义成了一个特殊的术语 happens-before ,满足 w(rite) happens-before r(ead) 的情形下,就要保证 w 的结果对 r 可见。这里其实是个反向的过程,既然某些代码和逻辑按照开发者的预期应该满足可见性,那就定义它要满足 happens-before 关系。(这里为了方便理解,对定义进行了简化)

这是最容易被误解的术语,它和指令执行的先后顺序无关,不是时间上的先后概念。(关于这一点如果不理解,建议从基础开始学习内存模型理论。)编译器的工作从来都不是对指令执行流程进行排序,只要能保证可见性,指令顺序是不影响预期结果的。

这个逻辑关系比较重要的特性是它具有传递性。我这里用 A <= B 代表 A happens-before B ,那么 A <= B 且 B <= C 可以推导出 A <= C 。另一点比较重要的是,happens-before 强制要求编译器保证可见性,但是可见性并不能反推出 happens-before 关系。

最终的结果就是,Golang 的设计者希望,当开发者用 XXX 的方式写代码的时候,编译器能产生符合预期的结果。于是就定义 XXX 这些写法,就是 happens-before 关系,编译器必须对其可见性结果做出保证。Golang 规范中定义了几种 happens-before 关系,这里就不一一列举了。

--------手动分割线--------

如果重新表述一下示例代码的问题,就是判断 [1] `a = "hello, world"` 和 [4] `print(a)` 是否满足 happens-before 关系,[2] `done = true` 和 [3] `for !done {}` 是否满足 happens-before 关系,从而判定可见性。

很遗憾根据 Golang 的规范,这里仅有 [1] <= [2] 和 [3] <= [4],不存在其他 happens-before 关系。(读 The Go Memory Model 原文可以看到采用 channel 通信后,比如把 [2] 和 [3] 分别替换为 `c <- 0` 和 `<-c`,由于语言规范层面上建立了两个 goroutine 相关指令间的 happens-before 关系,有 [2] <= [3],从而依靠传递性保证了 [1] <= [4],于是保证了不会有死循环,赋值也总能得到正确的输出)

既然不存在 happens-before 关系,那么编译器就没有必要保证 `done = true` 的结果对于 `for !done {}` 可见。缺少了这一层保证,那编译器的某种实现使得 `for !done {}` 一直读到 `done = true` 可见之前的结果也就不奇怪了。因为 `done = true` 对于 `done` 的操作可能完全不会让 `for !done {}` 知道。

--------手动分割线--------

看到这里你肯定会感到疑惑,为什么 Golang 不对前面这种情况做精确定义?

我的理解是,这个思路 C 当年用过了,然后发现太复杂且不符合直觉的情形太多,于是抽象了诸如 mutex/semaphore 等等原语来降低开发者的心智负担,帮助写出正确的代码。结果并没有那么理想,理解这个背景对于大多数非科班的开发者要求还是太高了。Golang 走相同的路不会有更好的结果。

Golang 的设计目标中很重要的一点是简单。希望即便开发者完全不懂内存模型,也能凭直觉写出正确的并行代码。原文的示例非常明显了,不要用 C 的思路去写并行代码,而是用 Golang 提供的 channel 机制,新机制有着严格且准确的 happens-before 逻辑,同时符合直觉。
用 rust 重新实现一遍很不错,手动点赞。

只是 https://github.com/mitmproxy/mitmproxy 已经很成熟了,用的是 python 开发所以很方便直接用 python 脚本接管请求处理。不清楚 rust 要怎么支持一个脚本引擎来做类似的工作比较合适。
春节期间我把开源可以部署的本地 LLM 都试了一下,没有量化单纯体感,7B 这个参数尺度上,DeepSeek Coder 效果是最好的。
能做成网络请求的方式是最好的,即使是内网也可以临时用穿透方式来测试。

如果因为实时性或者与其他模块集成没办法,那就编译成 pyd/so ,如果是 so 的话记得移除符号表,pyd 应该是默认移除的。

做一点简单的限时逻辑,不要有明显的特征,尽量以 silent crash 的形式来处理。这样一般防老板是够用了。有应对措施的大概率就能自己做了。
241 天前
回复了 zhoudaiyu 创建的主题 Linux 各位公司生产环境用的是什么版本的内核?
既然有选择的空间,那就肯定不是类似于嵌入式环境定制这样的场景了。

这个问题不如反过来思考,什么时候会考虑换内核?

因为绝大多数情况下,选择内核版本是在选择发行版之后的事情,也就是说几乎不会主动去换内核。

- 某个版本内核爆出严重安全漏洞

- 需要调节某个编译期配置参数(开启功能支持或性能优化)

- 闭源驱动模块限制内核版本

一旦限制了内核版本可选范围,那么就尽量选择改范围内的 lts 版本,然后就基本保持不变了。版本本质上是内核功能集合的数值化表述,不需要新功能(或者性能优化)就不需要更新。

目前还在维护的 lts 版本有 4.19/5.4/5.10/... 等版本,基本上应该满足各种需求了。使用 lts 版本的原因是,出现严重漏洞的时候,相关补丁也会 backport 到所有 lts 版本上。

另外发行版一般不会直接使用某版本 mainline 内核,而是有自己的一套补丁,用以配合自家发行版的默认功能。但是发行版的版本基本上会匹配同版本号的 mainline 版本,所以还是建议尽量选择严肃的发行版,以便尽快获得安全补丁。


------手动分割线------

尽管 CentOS 已经停止维护很久了,但还是有人会因为各种原因选择它。对于这种情况,不仅仅要关注内核问题,还要关注 glib 等版本的问题。

相应的开发、测试或者 staging 环境几乎都要退化到比较低的版本。比较新的版本就不存在这些问题。
我提一个可能会被遗漏的细节,微服务环境里做日志,一定要有全链路的 trace 信息,不然出了问题也很难还原出错的场景。

前面有人讲了读写分离,写基本都是本地,然后通过后台 daemon 定期汇总到日志服务器上。

原始日志汇总存储之后,建议再定期根据 trace 重新组织一下(比如数据库视图,或者直接用文档类型存储)以方便查询。一般来说,日志信息往往会比生产数据的容量还大,如果用到了再去查可能会很慢。
241 天前
回复了 onichandame 创建的主题 程序员 local first 应用前景咋样
某种程度上,现在软件商业化形式是和 local first 理念相悖的,所以做这个的很少。

从另一个角度,我个人认为开发 local first 应用的技术难度也很大,笼统地说实现 local first 就面临分布式系统的同步问题。这个问题没有最优解,只有妥协,当你追求用户体验的时候,留给你的技术选择就非常少了。
paddleocr 内存分配管理有问题是个很长时间的问题了。我印象官方 github 有几个 issues 就是讨论这个的,而且跨了多个版本,你可以去查一下。

之前有说是因为框架缓存的原因,有人说不是。我之前遇到这个问题也是通过重启来解决,尝试读了一下 c++ 代码部分还是太复杂了,没有解决的精力和能力。
246 天前
回复了 sofukwird 创建的主题 程序员 浏览器爬虫再进化
如果我没理解错的话,这个实现的是挺厉害的,只是应用场景非常受限。

从功能上说,相当于被注入的页面向外提供 http 代理,该代理会复用浏览器环境,也就继承了相关 cookies 。

使用这个方案,相比基于 webdriver/cdp 的方式,可以省去提取 cookies 、模拟登录的过程,通过人工在图形界面上操作一次即可。

比较容易想到的局限在于:

- 网站有多处或者基于访问频率的人机检测,就需要大量人工介入(甚至难以介入,因为通过代理触发的人机检测并不会显示在浏览器界面上)

- 目标接口需要额外访问参数,一般反爬措施都会利用 vmp 混淆 js 代码来生成可以被服务器后端验证的动态参数

如果我说错了还请 OP 指正一下,因为我确实想不到更好的应用场景了。
1  2  3  4  5  6  7  8  9  10 ... 12  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5482 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 36ms · UTC 08:50 · PVG 16:50 · LAX 00:50 · JFK 03:50
Developed with CodeLauncher
♥ Do have faith in what you're doing.