接口藏起来的成本,最后由谁承担?
零开销抽象、换一家内存分配器、在内部用并发:接口让调用者不必关心实现,但时间、内存和协调的成本并没有消失,只是换了承担者。
本文的问题 · 10
接口藏起的成本去了哪里?
同样一次哈希查找,可以顺着密集的数据往后探测,也可以追着指针去很远的地址。Carruth 的数据结构演讲让你沿实际访问路径计成本。把同样的问题带到他的抽象开销演讲:即使一个调用点产生了漂亮指令,成本也可能留在编译时间,或出现在跨函数的调用约定上。对调用者说「快」以前,先说明承诺的是哪种负载、哪道边界,以及谁在付费。
原文与时间戳 · 2
maximize the cache locality of of handling a collisionit just means the cost is paid somewhere else串行程序可以有多少种执行顺序?
把一个乘积展开,先乘左边还是右边,草稿不同,答案仍相同。Wolfram 借这个例子说明为什么要单独检查重写规则是否允许交换次序。Keller 面对的是一条真实的指令流:处理器找依赖、提前执行独立指令,还要记住哪些结果能按要求完成。两者之间的联系在于先声明外部允许观察什么。代数例子只比较最终表达式,处理器还需遵守内存等可观察行为,单看最后一个数可能漏掉违约。
原文与时间戳 · 4
you'll get different ordersyou'll always get the same answergo figure out the dependency graphwhat order should these operations finish in“这个任务不能读它”如何被发现?
两个工作线程这一次碰巧按理想顺序结束,屏幕上的一帧也可能完全正确。《命运》把另一种观察安插在开发版本中,检查任务依赖图,并在访问数据时核对权限。读者可据此改写一个测试问题。除了“最后一帧画对了没有”,再问“产生这一帧时,有没有人读取自己未声明可读的数据”。第二个问题才会在好运气的调度里留下证据。
原文与时间戳 · 2
identifying unsafe patterns, but it's going to be doing it during developmentevery time that you access the data, it's checking dynamically right there and then for whether or not you have permission框架的承诺,一定要由浏览器里的运行库兑现吗?
Harris 为模块边界补充了一个时间维度:开发者使用的声明式组件接口,不必原样成为用户浏览器里运行的机制。他在介绍 Svelte 3 时提出,把一部分组织工作放到构建阶段,让生成的代码完成页面更新;对过渡、绑定等可选功能,他的目标是只为实际使用的部分生成支持代码。这里要区分「工具提供哪些能力」和「某个应用实际交付哪些代码」。检查自己的抽象时,可以先列出调用方需要表达的意图,再问哪些决定在构建时已经可知、哪些必须等到运行时。这个例子扩展了隐藏实现决定的思路,不能仅凭演讲就保证任意程序都更小、更快;本文也没有复核屏幕上的代码或基准结果。
原文与时间戳 · 2
a framework doesn't need to be a thing that runs in the browserwe don't need to include the code that makes that feature possible内部按并发来想,外部一定要暴露并发吗?
Pike 先让扫描器和解析器各自保留控制流,再讨论怎样让调用方只看到「取下一个词法项」的接口。对模块承诺来说,值得分开记录的是:调用者怎样获得下一项,内部怎样保存扫描到哪一步,以及是否真的同时运行多个工作单元。他强调并发能够支持并行,却不要求并行;不能把设计中的独立活动直接改写成吞吐保证。问答里他承认没有测量性能,也愿意在性能关键时改用别的缓冲实现。因此可以把这个例子当作接口与内部组织可以分离的设计讨论,而不是某种通道实现总是更快的证据;本文未复核屏幕代码,也不沿用当时的初始化限制解释今天的 Go。
原文与时间戳 · 2
concurrency enables parallelism doesn't require itto be honest I have not measured it一次编译没有额外开销,承诺由谁负责?
Turner 的演讲为模块承诺添了一道验证边界。他描述一次加入 const 后的优化时,并没有把它说成所有编译器都保证如此,而是说明当时只有自己试到的一种编译器与标准库组合实现了结果。若调用者既要可读的接口,又依赖某种开销上限,就要把功能约定和实际构建中的成本证据分开。可以为项目写下工具链版本、优化选项、目标机器,以及变更后要重新检查的输出。这个检查思路来自讲者的限定,不表示本站已经重现了演示,也不说明对象、lambda 或生命周期封装在所有场合都没有开销。
原文与时间戳 · 2
the compiler happened to be able to optimize it.that was the only combination I found接口的简单,要由谁来维持?
Rob Pike 用 Go 的导入语句解释,调用方看到的一行代码背后仍有命名、作用域和链接要处理。Eskil Steenberg 从另一头描述同一项工作:先写出调用方需要的接口,再填入实现。这两种做法都把读者要理解的细节留给了库或语言的维护者。
Linus Torvalds 的问答给这个选择加上了时间尺度。内核维护者把已有用户空间程序能继续运行当作约束,演讲中也承认会出现破坏兼容性的情况。设计一个容易使用的入口,与日后让已有调用者继续使用它,是接口生命周期里的两项工作。
原文与时间戳 · 4
it interacts with things like scoping, naming, information hiding, isolation, linking,I start from the outside usuallywe don't break user spacewe do break user space sometimes换一家分配器拿到的内存,释放时怎么还回去?
Alexandrescu 用内存分配器说明,能否组合接口要同时看取出和归还两个方向。他在演讲的设计里用指针和长度一起表示内存块。主分配器满足不了请求时,可以让后备分配器再试一次。到释放时,同一个入口仍要知道该把块送给谁。他用主分配器的归属检查说明这种路由怎样实现。检查一个组合接口时,可以沿着同一块内存走完两程,先问谁有能力交付,再问哪项约定能把它送回原处。
原文与时间戳 · 2
it has the pointer and the lengthif you want to write an allocator you got to make it composableLisp 能生成机器码,就能直接调用现成的 C++ 函数吗?
Schafmeister 介绍 Clasp 时,分别交代了编译 Lisp 和连接 C++ 库的办法。Clasp 的编译器生成 LLVM 中间表示,再由 LLVM 生成机器码。要从 Lisp 调用演讲示例里的 C++ 函数,他还在绑定库里提供 Lisp 名称和 C++ 函数指针,让库在编译时生成包装器。追踪这次调用,可以在两处检查交接。编译器把什么交给后端?包装器又怎样把 Lisp 端可见的名称接到已有函数上?
原文与时间戳 · 2
implemented a new Common Lisp that uses LLVM as the backend.and it builds the wrapper function at compile time.泛型代码少写一份,专门化的成本由谁承担?
Smith 用 Coalton 说明,泛型接口可以把选择具体运算的工作交给编译器。编译器看到类型后,为相应组合生成专门代码。这条路径需要能取得源码或相应表示,也可能生成更多代码。理解演讲里的性能案例时,要把运行时间、编译工作与代码体积一起纳入比较。
原文与时间戳 · 2
much more than safety.you can't monomorphize if you don't have the source code.读完再想一步
挑一个你常用的抽象:它省下了什么,又把哪项成本推给了编译器、运行库或别的团队?