V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  kuanat  ›  全部回复第 8 页 / 共 12 页
回复总数  233
1  2  3  4  5  6  7  8  9  10 ... 12  
字体渲染这个话题一两句话说不清楚,我自己的笔记大概有五六千字,如果有空闲我会考虑专门发个帖子。

这里回复主要是想表达一个观点:苹果的渲染效果是可以在 Linux 环境近似复制的。

我这里举两个例子,需要配合 #23 @agagega 的截图对比来看。

由于我这里没有苹方字体,不过看过效果之后就知道不影响了。我这里的环境是 Gnome+Firefox 200% 缩放。

第一张是我日常的效果

https://i.imgur.com/E7QDq7p.png

第二张是贴近苹果的效果(通过调整参数还可以更像)

https://i.imgur.com/3FNIPYO.png



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

Windows 那个就不做对比了,我说下 #23 macOS/KDE 和我这里的区别。

首先忽略标题里粗体,那个是字重的关系,其次忽略英文和数字,因为它们 fallback 到了不同的字体上。

如果你把图片保存下来放大看,那么你会发现,KDE 的字体渲染是有彩色边缘的,说明它使用了 subpixel 抗锯齿。而 macOS 实际上是用的 grayscale 抗锯齿。

接下来关注标题下面“XX 小时 XX 分钟”的那个“时”字,苹果系统那个“时”和日常里写法是不太一致的,右边“寸”的横非常靠下,而 KDE 版本就比较正常。这是因为 macOS 的渲染引擎会无视 hinting ,而 KDE 依赖的底层 FreeType 在 KDE 的默认值是 Slight Hinting ,当苹方字体本身没有 hinting 信息时,会按照算法在垂直方向上做自动 hinting 修正。

所以 Linux 环境里,如果通过配置 fontconfig ,设置 hinting=false hintstyle=hintnone ,基本上可以获得接近 90% 的苹果效果。也就是我这里图一的例子。

如果仔细看还是会发现,苹果的渲染标题下面“XX 小时 XX 分钟前”里面的汉字,明显要颜色偏深一些。实际上,如果设置了暗色模式,这个差别会更明显。

这里涉及到一个叫 stem darkening 的概念,在“正确”渲染的前提下,额外对灰度进行加深,以匹配人类视觉的非线性感知。

所谓“正确”是指,通过 alpha composing 将字符前景与背景合成,这个计算是在线性空间里完成,然后通过 gamma 矫正转换到输出空间。这个过程在 Win/macOS 上都是默认支持的,然而 Linux 缺少 gamma 矫正这一步。

可以通过 FreeType 的参数来强行启用 stem darkening ,就如我第二张图的效果。实际效果是轻微的加粗,对于灰度显示的部分影响比较明显。只是从“反映字体设计意图”的角度上说,这个渲染逻辑是不正确的。但考虑到追求渲染效果往往是因为设备 DPI 不够,所以正确与否反倒不那么重要了。
`Run or raise`
https://github.com/CZ-NIC/run-or-raise
https://extensions.gnome.org/extension/1336/run-or-raise/

PS
不依赖扩展的方式就是楼上说的 Super+数字,上面的插件里也提到了。

再 PS
tiling wm 环境里这个功能非常好实现,可以借鉴一下思路。绑定快捷键就用 Gnome 自带的 Keyboard shortcuts ,执行一个 bash -c "command" 命令。这个命令脚本用来判定运行程序和判定焦点,然后通过某个接口去操作窗口管理器。X11 的话,wmctrl 就可以。Wayland 需要通过 D-Bus 调用。
258 天前
回复了 flx413 创建的主题 程序员 QUIC 实践疑问
基于你这个描述,我提供一个方向,可能和我之前遇到的丢包问题是一样的。

并发加载资源的时候,NWListener 用单个 NWConection 连接会丢包,用多个 NWConnection 来处理,有可能就正常了。

一般化的测试方法是用 quic-interop-runner 来跑一下,看看是不是协议不同实现的问题。估计你用的服务端实现肯定在列表里,就是需要额外写个 ios 的客户端。

如果要定量测的话,可以配合 wireshark/mitmproxy ,这俩新版本都可以简单处理 quic 协议了。
Go 的 context 是 1.7 版本引入给 net/http 服务的,用来解决信号和取消问题,传 value 只是顺带的,同时特别强调了线程安全的问题。名字用了 context 但是语义上确实只有上文。所以当你真正需要上下文的时候 context 包是不够的。

一般中间件解决这个问题的思路是自定义 context ,其实我不太喜欢 gin 的方式,我个人的偏好是类似

```
type MyContext struct {
ctx context.Context
// custom field
key string
}
```
这样的方式。然后实现 Context 的接口方法,写几个 wrapper 就可以完成对 context.Context 的兼容,不影响原本 net/http 的信号取消机制。

剩下的就是语法层面的封装了,需要实现一组方法,比如从 context.Context 衍生出子 MyContext:
```
func DeriveMyContext(ctx context.Context) *MyContext {
myCtx, _ := ctx.Value(MyCtxKey).(*MyContext)
return myCtx
}
```
此处用接口断言是根据 context 的设计,value 通过自定义类型模拟命名空间,防止 key 冲突。

结合起来就是 `context.WithValue(context.Background(), key, value)` 中的 kv 对,实际上就是通过 context.Value 传递了一个特定的 key ,这个 key 等价于指向 MyContext 的指针,和你的思路是一致的。

这样中间件所有涉及的 context 都通过一个 MyContext 的结构共享上下文,如果涉及到多线程可以加 Mutex 锁。

反正 Go 在传递 context 这件事上已经一条道走到黑了,比如 1.21 标准化的 slog 日志库也可以接受 context ,稍微封装下也可以直接用。
323 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@cnt2ex #63

我的描述是基于自己的记忆和理解,不一定就是正确的。

我原本是想要通过 __all__ 这个例子来体现,基于文件系统的 import path 对今天包管理机制造成的困扰。

在当时 windows/dos/Macintosh 的文件系统上,import * 遍历出来的文件名是大小写不固定的,也就导致了引入的符号表大小写不固定。于是设计了 __all__ 机制,因为 __all__ 定义的大小写是可控的。开发者可以选择 opt in 。

__all__ 机制是需要手动维护的,也不能阻止用户显示地手动引入。如果今天重新设计一门语言,__all__ 是完全不必要的,可以通过 public/private 关键词,也可以通过某种 name convention 来实现。

并不是说基于文件系统的 Package 组织形式不好,而是说这是一个选择。Python 选择了简洁,但是要在别的地方(包管理)付出代价。__all__ 机制就是这个代价之一。


回到虚拟环境的问题上,现在假想一下 Python 依旧使用基于文件系统的 Package 组织形式,但是在路径名中包含版本号。

现在 Python 就面临几种选择,要么以 import xyz==1.1.1 的形式使用,要么 Python 自己实现一个版本选择机制然后维持 import xyz 的向后兼容。

第一种方案几乎没有人选的。因为一旦要升级依赖,需要显示地在源代码里面做修改。但是这个方案的改版非常多见,比如 npm 的 package.json 就是集中把 xyz==1.1.1 放到了一起。

(看起来很完美对吧,但是当时没有人意识到这个 NP 问题会成为日后的麻烦。这种组织方式从机制上就不允许引入同一个包的不同版本,所以 NP 问题退化为 P 问题的途径就少了一个。)

第二种方案,考虑到时间节点,不是一个好的选择。但这个路线是近十年来以软件工程为导向的语言的首选。

这里需要明确区分 Package 的组织形式和 import path 的实现,前者是一种 specification 规范,后者是一个实现方式。Python 完全可以从规范层面要求所有的包名都带版本号,然后 import hook 的实现无视它,固定使用版本号最大或者最小,甚至文件最后修改信息最新或者最旧的版本。


虚拟环境解决的是:在 Package 组织形式是基于文件系统、且 Package 规范不包含版本号的前提下,提供一种在同一个系统中,安装同一个包多个不同版本的可能性。

如果 Package 规范要求包名带版本号,那么“安装”这个行为是不受限制的,也就不需要虚拟环境了。至于安装好了之后,使用时“选择“哪个版本,这是另一个事情。

技术层面上,我猜 Python 转向 npm 类似的包管理方式是没有太大难度的,PEP 621 规范了 pyproject.toml ,也就是前面说的方案一,楼上 frostming 开发的 PDM 就是这么做的。现在的问题是 Python 的 import hook 本身并不支持路径名带版本号的,所以还是需要再套一层虚拟环境,让 Python 只能访问到特定的包。

说得再具体一点,包管理器在文件系统里,以包名+版本号的形式,集中管理所有的包。虚拟环境指定了 $PATH ,在当前环境中的包软链接到实际的带版本号的包,然后软连接的名字不包含版本号。包管理器额外维护一个 pyproject.toml 记录当前所用的包,同时负责计算依赖。在此环境内的 Python 解释器和源码都不需要做任何改变,完全向后兼容。
325 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@flyingghost 60

我没有听说过类似的方案,如果让我评论的话,我认为两个方向都不合适,一个是结合了版本号之后命中率很

另一个方面,一个包 A 如果同时依赖 B 和 C ,我猜测不存在一个多项式算法,可以从 B 和 C 依赖计算出 A 的依赖。(需要 backtrack 到所有依赖重新计算)导致这样的缓存中根本上不可用。
我的映射是 capslock 短按是 esc 长按是 ctrl 。
326 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@cnt2ex #57

这里我说得太简略了。

在设计这个 import 机制的 1.x 版本,要实现 import * 需要去文件系统里遍历有哪些模块。底层文件系统可能会对某个名为 xyz 的模块,返回 XYZ/Xyz/xyz 几个不同的结果。当时对于这个问题的解决方案是设计 __all__ 让维护者自己声明是哪一个。

之后的 PEP 才明确了模块名应该( should )全小写,import 的所有符号都是小写。再之后 __all__ 才成为一种工程上的控制机制。
326 天前
回复了 Goooooos 创建的主题 Java 吐槽下 Google 开源的组件
@Knife42 #8

自我定位是 long time lurker 所以没有什么社交账号。

v2ex 发帖的初衷是想在 AI 生成内容的时代留下一点有价值的东西。
326 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@frostming #54

我又看了一遍自己写的东西,其实不太准确,我这重新描述一下。

版本选择之所以是 NP 问题,源于好几个假设。其中有几个假设是没法改变的,能够改变的还有两个:

- 一个包可以声明自己依赖零个或多个特定版本的包。
- 不允许同时选择同一个包的两个不同版本。

第一条过于严格了。如果把特定版本换成一个范围,这个问题就简单多了。第二条也可以适当放宽,和第一条一样,都是建立在 semantic versioning 主动向后兼容的假设之上。虽然不能引入同一个包的 1.X.X 和 1.Y.Y ,但是可以同时引入 1.X.X 和 2.Z.Z 。

这两个条件放宽之后,NP 问题就不存在了。但是对 APT/RPM/Node/Python 这些起步较早的社区来说,这两个要求依然太难了。

至于主包覆盖依赖的限制,这是用来解决菱形依赖的,不是规避 NP 问题的核心措施。还是之前的例子,只要保证 C 符合 semantic versioning 向后兼容就好了。这个措施实际体现的是解决问题的思路转变。

具体的来源比较分散记不清了,我凭印象做个总结。第一个做 NP 规避的应该是 Rust/Cargo 。之后 Go 做了一个叫 Dep 的类 Cargo 实现,实践了一段时间,吸取经验教训,重做之后形成了正式的 Go mod 方案。Rust/Go 用相同的方法解决菱形依赖,但是基础思路是不一样的。这一点从 Rust 总是选择最高版本,而 Go 总是选择最低版本能看出来。

这里想象一个场景:A==1.1.0 依赖 B==1.5.0 ,之后 B 发布 B==1.6.0 ,由于 B 引入了非兼容改变,导致 A==1.1.0 无法依赖 B==1.6.0 构建。这个时间节点,按照 Rust 的设计,所有依赖 A 的用户(包括依赖 A 的老用户)都会受到影响,而 Go 这边只有同时依赖 A==1.1.0 和 B==1.6.0 的新用户(老用户不受影响)才会受影响。

之后 A/B 至少有一方要打补丁发新版。Rust 认为要么 A 在 B 没有提供补丁的情况下,主动发新版声明不兼容 B==1.6.0 ,要么 B 发新版修复对 A 的支持。实际上在开源社区这两个事情都是很难的。Go 的设计是除非用户主动升级,都会保持作者发布时的最低依赖版本,(毕竟发布那个时刻的版本依赖几乎是都可以构建的)这样就给 A/B 争取了非常多的修复时间。

Rust 的设计者更希望为开发者提供最好的体验,希望一己之力解决所有问题。Go 的设计思路是依靠社区合作,依赖所有人主动去帮助解决对自己来说比较容易,而别人不好解决的问题。所以你能看到,Go 官方一直不遗余力地推动向后兼容,因为 Go 的整套实现逻辑都强依赖整个社区对于兼容性规范的共识。



最后赞美 pnpm ,感谢!
327 天前
回复了 hankli 创建的主题 程序员 试一下用 VersionFox 替代 asdf-vm?
@hankli #7

我一开始没注意到 VersionFox 是跨全平台的。为了兼容 Windows/PS 的话,确实单可执行文件比脚本靠谱。(我觉得理智一点的开发者,在 Windows 环境不用 WSL ,也应该用 MSYS2 吧哈哈)
327 天前
回复了 hankli 创建的主题 程序员 试一下用 VersionFox 替代 asdf-vm?
支持一下~

asdf shell 一直都有,global/local/shell 都支持。我记得 shell 的实现比较粗暴,就是类似 ENV=xxx cmd 的方式。

shim 机制的问题在于需要 reshim ,比如 python pip 安装了某个可执行文件,需要 reshim 才能在当前 shell $PATH 索引到。极小概率的情况,比如某些程序运行后释放执行脚本,又硬编码 #!env python 这样会导致出错。

asdf 有个 direnv 插件,可以管理环境变量,解决每次执行都要 shim 查找的问题。(话说慢真是个问题吗?)

asdf 的优点是插件可以比较方便复用已有的构建脚本,本地构建安装而不是下载二进制文件。

另外我个人比较喜欢这种脚本都用 git 做管理的模式。
327 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@kuanat #47

再做一点补充。

Python 3 是 2008 年左右发布的。这个时间点上,如果 Python 决定重写 import hook ,将版本号纳入成为包名的一部分,支持安装同一个包的多个版本,就没有今天虚拟环境什么事了。

这个改动和“不做官方包管理”不冲突。不做官方包管理是正确的选择,在那个时间点上,很难做得好,特别是这些实现都要用 C 来写。

但是底层限制所限,同一个解释器环境不能安装同一个包的多个版本,那包管理器是永远无法摆脱虚拟环境的。
327 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
“为什么要有虚拟环境”这个问题比较好回答,就是为了隔离。我觉得楼主真正想问的是,或者说这个问题有意义的点在于,为什么 Python 没有“现代”一些的包管理机制?

楼上已经有不少人提到了,Python 不支持同时安装一个包的多个版本。要解释“为什么”不支持,那就比较麻烦了。

这要回到 Python 最早设计包这个概念的时候,当初的决定导致了,不可能在不做 breaking change 的情况下,改变这个行为或者说添加版本机制。这个设计决策的时间点在 1.X 版本,估计大部分人都没有用过,好在 PEP 文档还是可以一窥设计思路的。


我这里提个看上去无关的问题,为什么 Python 需要 __all__ 来支持 from XXX import *?

答案是 Python 的 import 机制是基于文件系统的,包名就是路径名。基于文件系统就意味着,包名无法区分 XXX/Xxx/xxx 的,因为文件系统不确定是否是大小写敏感的。

你可能会觉得包名就是路径名,那给包名后面加上版本号不就行了吗?理论上是的,很多现代一点的语言的包管理就是这么做的。那 Python 为什么做不到?主要原因是 Python 流行起来之后,不可能在不影响生态的情况下做这样的改动了。理论上 Python 2/3 的时候有这个机会,但是 Python 没有做这个变动,这与 import 的实现机制有关。

Python 的 import hook 包括标准库,都涉及到自举( bootstrap ),所以是用 C 写的。更重要的是,增加版本号意味着 Python 要官方实现包管理机制,而包管理是个理论和实践都非常难的事情。Python 选择继续采取社区的“虚拟环境”方案,我个人认为这是个正确的选择。


这个问题到这里其实就差不多说清楚了,不过我打算再补充一些内容。

包管理问题的难点在于版本选择,目的是要找到某个包的完整的(所有)、兼容的(不能包含相冲突的,比如同一个包的两个版本)依赖。这个问题有多难?我印象大概 2017 年才有证明,这是一个 NP 完全问题。NP 完全的意思就是,不知道是否存在一个可以稳定在多项式时间内,完成这个解析过程的算法。

所以几乎所有的传统包管理软件只能做个二选一,要么选择正确但是有可能很慢,要么选择时间可控但是可能出错。实际上几乎所有的包管理都选择正确但是慢,或者说正确但是不清楚要花多长时间。前面的证明给了包管理器新的设计思路,要增加限制条件或者说妥协,不去追求完美解决 NP 问题,只做工程上“好用”的解决方案。

具体理论不展开了,我这里简单说一下“现代”包管理的两个基础假设。一是高版本总是向后兼容低版本,不兼容的情况使用 semantic versioning 的 MAJOR 区分。二是主包可以改写间接依赖的版本。第二条不好理解,举个例子:A 是开发者要构建的包,这里叫主包,它直接依赖 B 和 C==1.2.0 ,其中 B 又依赖 C==1.1.0 。这时候要构建 A ,那么 A 对于 C 的要求就会覆盖 B 对于 C 的要求,又因为 C 的 MAJOR 版本没有变,理论上 C==1.2.0 是同时满足 A/B 需求的。当然单独构建 B 的时候,B 作为主包,依旧会使用 C==1.1.0 的版本。这一条放宽了之后,NP 问题的限制就不存在了,这个问题就有了多项式级别的解法。

我接触过的包管理,似乎只有 Rust(cargo)/Go 是基于新理论(即主动规避 NP 问题)的。区别是 Rust 总是选最高版本,而 Go 选最低版本。Go 的实现比 Rust 简介很多很多。其他的包管理都还在跟 NP 问题作斗争。
327 天前
回复了 Goooooos 创建的主题 Java 吐槽下 Google 开源的组件
很多时候不兼容的改动都是“无意识”的,开发者以为修改是向后兼容的,但实际上不是。这种错误我犯过不知道多少次了……


关于向后兼容这个话题,我推荐两个讲座,都有视频和讲义:

- How to design a Linux kernel interface, https://archive.fosdem.org/2016/schedule/event/design_linux_kernel_api/

- Detecting incompatible changes, https://sourcegraph.com/blog/go/gophercon-2019-detecting-incompatible-api-changes

简单做个总结。Linux kernel-userspace API 早期的失误造成的影响已经持续了几十年,很多还将继续影响下去。这些失误的来源多数是设计者想象不到下游开发者会以什么样的方式去使用这些 API 。

规避的措施就是自动化测试,而自动化测试需要对 specification 进行规范化。实际上推动了开发流程的改变,即先公开规范和 API 设计,提供实例代码,接受反馈并修改设计方案,最后再实现功能和测试。而不是过去的先做实现,然后公开 API 接口的模式。

(这个事情做到极致大概类似于 amazon ,先开发布会,然后再开发)

第二个讲座虽然是 Golang 的,但指导意义很大。核心是如何从技术上自动判定某个改动是否会影响兼容性,进而对如何编写“面向未来可兼容扩展”的 API 接口提供理论支持。Golang 的开发团队为 Go1 的兼容性做了很大的努力,有非常多的案例经验可以参考借鉴。
329 天前
回复了 rivercherdeeeeee 创建的主题 骑行 自行车选择合理推荐
我推荐个迪卡侬 Gravel 120 ,官方价 3999 的样子。迪卡侬自有品牌性价比比较高。

这个型号一般叫瓜车,通过性介于山地和公路之间,城市间骑行偶尔有烂路什么的问题不大,比公路好多了。

车架几何比较舒适,只有单盘,齿比只能说 40 速度够用。再就是这个价位因为套件什么的都比较省成本,装配看脸,多让师傅调一调。
我提供一点额外的视角,有关面试的。这个活动前两天上了 Hacker News 的头条,长期看 HN 的话会注意到类似的活动还有其他语言的版本。

很早之前我司有个内存远小于数据集的版本的同类面试题,后来我主张给砍掉了。原因有两个方面,一是区分度不高,用在校招上,绝大部分人难以回答应用相关的优化,都集中在算法上;用在社招上,脱离了主要应用场景,和八股文没什么差别。另一个方面是开放式问题对面试官要求太高,对于不在预设方向的答案难以量化打分。



回到这个题目本身。

纸面上的问题有纸面上的解法,现实里的问题有现实的应对策略。可能直觉上这是个算法题,但实际上作为比赛,那就是算法、经验、工程、原理和细节多个方面的事情了。

- 经验

社招的技术面试更看重经验,但是经验这个东西很难准确量化,甚至连标准化量化的手段都很少。

我个人对于经验的定义是“分析、定位问题的能力”,这个能力是在技术之外各行各业通用的。一般来说,能准确地用技术语言描述问题,用客观可验证的手段确认问题的核心所在,那问题就已经解决了大半。

所以我在面试里更喜欢问一些类似 profiling/debug 工具、流程的细节,因为我觉得愿意用工具、数据辅助分析,水平是要高出凭印象去解决问题的,当然这是我的一家之言。

这个投递第一天上 HN 的时候,最高的讨论是有关 IO 瓶颈的,然而这个方向是错误的。即便是 HN 这个水平相对较高的社区,低水平的讨论也是大量存在的。当然现在再去看经过社区筛选的高赞结果,就比较有指导意义了。

- 工程

考虑测试环境是 Linux 而且 RAM 32GB > DATASET 12GB ,整个测试跑五次,去掉最高最低结果。除非这个程序特别离谱,占用了超过 20GB 的内存,那么绝大多数情况下,这个文件已经在 page cache 里面了。所以跑一次是 IO 问题,跑很多次就不是 IO 问题了。

一个非常 naive 的单线程实现大概是 240s 左右,不考虑其他因素的话,8 核心测试机上可以预期到 30s 的水平,观察一下榜单,基本上 60s 以内的方案都用到了并行处理。

把工程因素放到最前面说是因为它以最低的成本实现了最大程度的优化。但是这个事情作为面试题是不合适的,因为掌握多线程是最基础的,面试的目的并不是为了区分会不会多线程。

- 原理

这个场景确实存在 IO 瓶颈,但它发生在 kernel/userspace 之间,而非 RAM 与外置存储之间。用户态 read() 需要 syscall 将 kernel 管理的 page cache 里的内容复制到用户空间。这个行为越多,效率就越低。而 MMAP 机制绕开了这个 context 切换,所以提供了真正的 IO 层面的优化。

如果再观察一下榜单里的实现,基本上 30s 上下的方案里,都用了 MappedByteBuffer/FileChannel 之类的调用。换句话说,理解 MMAP 可以在多线程的基础上,稳定将结果优化到多线程的理论上限。

但这个筛选条件略微苛刻了,要么需要比较好的科班基础,要么就要恰好做过类似的业务。最终在进入技术面的数量有限的人群里,能准确回答的寥寥无几,导致缺乏区分度,反正大家都不会。(不是水平问题,而是几率问题)

再往下就是字符串处理了,我相信如果跑个火焰图看一下,30s 上下的方案性能热点应该在自定义的 parser 上。

因为这个竞赛是 Java 的,开发者更习惯的是用 Java 提供的高级抽象,相关的实现都是内存/线程安全的,但是会因为各种检查变慢。

这里不好说到底能优化多少,但是 25s 以内的实现方案里的 parser 基本都是扫描特定字符,而不是用标准库字符串方法了。10s 左右的方案几乎都考虑到了内存分配、变量重用等等技巧。但是这样非常具体的案例在面试里是没有可考察性的,除非应聘者自己主动提出了这个方案。

- 算法

终于轮到算法出场了。对于这样一个场景,算法唯一的要求就是 one pass ,意思是边读取边计算已经读取过的结果里的最大、最小和平均,而不是完全缓存之后再统一计算。

如果是求中位数的话,我觉得倒是可以拿出来讨论。我不清楚是不是有完全准确的 one pass 中位数算法。回到现实里,对于这么大的数据集,求解目标要不要完全精准有可能在数量级上影响实现效率。所以真正考察算法的时候,我会偏好问 bloom 过滤器之类的方向。

这其实也是整个问题里我相对认可的一个点,与其空泛的问 MapReduce/Streaming 之类的,完全不如考察在解决其他问题时,展现出来的触类旁通的能力。只是这个问题又太简单了,而且性能提升很小,甚至性能提升的主要来源是避免了内存分配和 GC 。

多数时候我们都希望开发者有算法基础,但是实际应用里却不希望开发者手撸算法。面试过程里考察对于算法的理解更多体现在应用层面,知道并理解常见算法的复杂度上下界,以此可以来指导方案选择。

但现实的问题是,要么大家都会要么大家都不会,就比如这个场景里,所有人都会选择 hash 结构用来存储结果,所以没有区分度。

- 细节

进入到 10s 水平,再往后的优化几乎就是抠实现细节了。

这个层面的优化,和问题场景强相关,意思就是这些手段是难以复用的。对于 C/C++ 背景的开发者比较好理解,算法逻辑是算法逻辑,数据检查/判断分支是另外的事情。所以剩下的操作无非就是:用 unsafe mmap 替代安全的映射调用,用简化的 hash 方法替代标准实现等等。

还有一个方向是向运行环境做匹配,考虑指令集、cache line 等等微观特征。考虑这里的瓶颈不在 cpu ,我怀疑 SIMD 的方案的有效性。到了这一步,看结果的话又提升了 30%,但实际生产环境可能没人会这么奔放吧。作为面试题来说,能聊到这一步应该 100% 过关了。

这里再补充个插曲,之前有别的面试官面过一个 OI 出身的应聘者,提了一个基于状态机的方案,然后当场把面试官说懵了。后来几个人回看面试记录,证明他说的方案是没问题的。这就是我说的开放性问题对于面试官的要求太高了。

行业里的开发者,一般非科班出身很少会接触状态机。主要原因是业务上需要状态机相关算法的场景非常优先,以状态机作为优化手段的可移植性又太差,除非特别核心的逻辑不得不用,很少会主动选择。



反正现在的就业环境就这样,面试造火箭。我写这些东西就是提供个思路,其实把视角拉远一点,这个具体的题目没有多少值得讨论的东西,解决问题的逻辑更加重要。
339 天前
回复了 Cooky 创建的主题 Linux Arch 大危机, Gentoo 开始提供二进制包
Gentoo 提供二进制预编译内核大概一年多了吧,这个比较重要。相对来说软件包二进制版本没那么必要,毕竟把 Gentoo 当 GUI 主力的用户,基本都有自己的编译机。

我用 Gentoo 的时间也有十几年了,但我的主力桌面系统是 Fedora ,Gentoo 更多是个 meta 系统,用来完成特定任务和功能的。

放到十年前,我会把 Gentoo 当作服务器的主力系统跑,现在更多是用它来作为基础构建系统,来打包特定的软件,做成容器镜像使用。

另一个用途也还是当作构建机器使用,用来配置交叉工具链,编译一些嵌入式设备或者不同架构上的应用。主要是考虑到它基于源代码的特性,一方面比较好排查依赖相关的问题,另一方面 use flag 比较好做版本和特性 pinning 。

还有一个重要特性是 prefix ,用来在 mac 或者主力 linux 上,管理那些需要自行编译、不在官方源当中的软件。
单纯映射上下左右有很多方法,一般要么是 asdw 要么是 hjkl ,这个改键可以从系统层面全局做。如果只是在编辑区用,多数都是类 vim 的插件方式。

但是 IDE 层面,没有哪一家真考虑过对纯键盘做支持。IDEA 不行,VS 也不行。即便它们都有类似切换显示界面的功能(比如开启、关闭文件列表区、内置终端),但是都没有输入焦点的设计,展示了对应的界面,输入焦点不一定能切换过去。

再就是缺少统一的快捷键逻辑,比如现在的输入焦点在内置终端里面,那很多 ctrl 的快捷键组合就会和 IDE 本身冲突了。
反向端口映射

adb reverser tcp:80 tcp:8080

第一个 80 是模拟器里面的,浏览器访问 localhost 80 会转向 host 8080 。
1  2  3  4  5  6  7  8  9  10 ... 12  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5479 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 29ms · UTC 09:00 · PVG 17:00 · LAX 01:00 · JFK 04:00
Developed with CodeLauncher
♥ Do have faith in what you're doing.