V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  secondwtq  ›  全部回复第 32 页 / 共 121 页
回复总数  2420
1 ... 28  29  30  31  32  33  34  35  36  37 ... 121  
2021-10-19 02:17:04 +08:00
回复了 sghwn2 创建的主题 酷工作 快手 Syslab 团队招聘新编程语言开发专家,编译优化专家
新的通用编程语言,搞这个不多哦...
2021-10-19 01:45:51 +08:00
回复了 Liang 创建的主题 MacBook Pro 我们为 Macbook Pro“增加”了更多的接口
哈哈哈,接口,TouchBar,MagSafe,全都 revert 回去了
2021-10-18 20:53:24 +08:00
回复了 gossip 创建的主题 Python 如果存储运算形成的“中间量”
没用过 Stata,不过看主题内容,感觉楼主是混淆了“文件”这个操作系统 /文件系统的概念和编程语言自身的概念。

大多数常见编程语言都是围绕“值”“表达式”“函数”“类”“接口”这些结构折腾。这些结构构成了一个编程语言的核心部分。编程语言的实现,以及该编程语言使用的库,可能会引入新的概念。比如 C++ STL 库里引入了臭名昭著的“迭代器”概念,而一些做数据分析的库,比如 pandas,就引入了“Dataframe”和“Series”的概念,这个和具体库的 domain 有关系。

写一个程序的过程,可以理解为:
1. 首先定义一个问题
2. 然后再将问题递归地拆分为更小的问题
3. 最后使用编程语言或库中的相关结构和概念实现这些问题的解决方案

这么一个过程,或者说通过分治解决子问题来解决更大的问题,这些子问题及其对应的解决方案就可以被称为“模块”(注意这部分的“模块”和 Python 中的“模块”结构没有直接关系,后面会解释)。能够清晰合理地划分模块的边界,并且将同一模块的东西攒在一块,叫模块化( modularity )。

楼主说的“两个功能的完全不同”“逻辑清楚”就是在实践模块化。

模块化主要是定义及划分模块,这些工作做完之后,最后还要有一个把模块重新拼在一起,由解决小问题到解决大问题的过程,这个过程称为组合( composition )。组合的过程简单自然称为组合性( composability )。

当然具体做这个的过程中,由于计算机没法直接理解你的问题和子问题,所以到了干苦力那一步的时候,还是要落到编程语言和库自身的结构中,具体来说就是通过这些结构的组合来“编程”,也就是刚才的步骤 3 。就是说这些结构也是可以组合的,在大多数编程语言中,上述提到的“值”“表达式”“函数”“类”“接口”这些结构都能相对容易地组合。设计良好的编程语言和库,最重要的一点之一就是其结构或模块容易组合。

“编程”的过程,最后的产物叫“程序”。我们说它是“编程语言中结构的组合”,是因为计算机只理解编程语言。我们把这些结构附加上人类能够理解的意义后就成为我们刚才说的“模块”。楼主产出了 A 和 B 两个文件,这两个文件在计算机看来就是 Python 的类、函数和语句这些结构,而在人看来则是用于解决一个问题的两个子问题的两个模块。

可见这里存在一个从计算机编程语言的结构,到设计层面的模块的映射。这个映射其实是相当自由的,也就是一个公式可以是一个模块(比如它解决了计算某个期望值的问题),一个正则表达式可以是一个模块(比如它解决了判断是否是合法的 Email 地址的问题),一个类可以是一个模块(比如它解决了访问数据库中某种实体的问题),一个函数可以是一个模块(比如它解决了数据加密的问题),甚至一个函数中的单独一段代码也可以成为一个模块(比如在加密数据时,判断是否应该在源数据尾填充零数据以满足对齐要求)。

很多编程语言,包括 Python,单独做了一个叫做“模块”的结构。这种“模块”结构,解决的其实是“我有多个相关的结构,设计上属于一个模块,该怎么放在一起”的问题,也就是一般用于表示一些更加基础的结构的集合。比如 Python 中的模块可以包含变量、函数和类。

刚才其实有个小问题,我 composition 的单位,究竟是“模块”( module ),还是“组件”( component )?从上一段的例子可以看出,似乎人们(至少 Python )更喜欢把具有“集合”性质的东西定义为 module,这个我叫狭义的 module 。而对 component,似乎有一种观点是高层设计上的划分叫 component,执行层面代码实现的产物叫 module 。之所以说是小问题是因为我认为这个这俩基本是同义词,至少当我们讨论 modularity 和 composability 时没有必要区分,不管 module 还是 component,都是指模块化设计产生的东西,它们也应该可以组合,不一定是某种集合才能叫 module 。比如一个函数本身不是集合,但是每个函数确实有明确定义的接口,会完成特定的工作,通过函数来组织程序,相比于直接一口气写一大段确实有更好的 modularity 。(更需要区分的其实是 modularity 和 composability:前者是把问题划分为模块,后者是把模块重新组合成问题)

这就是为啥刚才说我们讨论的“模块”和 Python 的“模块”不是一个东西,前者是一个特定的产品中为了解决某个专门的问题的结构。类似的限定于单独产品的词还有很多,比如 React 框架里面 component 表示的是 React 实现的 UI 模块,Linux 内核中 Kernel Module 表示的是一种动态扩展内核功能的工具,微软的 COM 中的 component 则试图在不同机器和不同编程语言之间建立一个共有的结构,这些“模块”和“组件”的含义都是在专门的 domain 下的。我们谈的“模块”是通用的。

---

之所以要费这么多字辩经,是因为很明显这个有搞混的可能 ... 不过大部分人都应该知道区分同一个词在特定语境和通用语境的差别,所以如果不考虑我刚才那套“广义模块”和“狭义模块”的歪理的话,并不容易搞混。但之所以我还要强行输出“广义模块”和“狭义模块”这套歪理,当然是因为我拿了拜登的经费(还有别的可能么?),有意要塞私货——我并不喜欢(看上去)非常流行的“狭义模块”定义,我也并不喜欢很多常用编程语言中特定的“模块”结构,这些问题导致了楼主的困惑(也导致了很多其他问题),而最好的解决方式就是把“模块”的默认定义切换到“广义模块”。

关于“狭义模块”定义本身的问题,刚才已经有说过了。到了编程语言层面,对“狭义模块”的实现,又是各自有各自的问题。首先是该不该有这个单独的结构,变量、函数、类这几种常见的结构,都可以用于实现“狭义模块”,但是很多编程语言为了“狭义模块”单独做了一个结构,很有种多此一举的感觉,但是这么做的不在少数,是因为语言设计者的剃刀都钝了么?这个得具体来分析(注意下面的例子中的“模块”都指狭义模块):

Python 中的一个“模块”就是一个文件(有使用文件内代码创建模块的方法,不过很少用),几个模块在文件系统中组合在一块叫一个“包”( package ),这个意义其实就是把模块再组合一遍。
Go 中一个文件夹算一个“模块”,同一个文件夹中的名字默认是互相可见的。当然 Go 里面相关结构的名字和其他不太一样,Go 貌似把一个文件夹叫 package,几个 package 组合到一块再叫 module 。
Swift 中一个“模块”指的也是源文件的集合,一般以“framework”的形式出现,和 Go 类似,这个集合内所有的符号是互相可见的,单个文件并不构成一个“模块”。
Bash 中不存在明确的“模块”结构,最接近的就是文件 ... 文件本来就是 Bash 的核心结构
C 本身不存在“狭义模块”层级的结构,但是有个预处理器,预处理的就是文件,而所谓“头文件”基本代替了模块接口的作用。C++ 有 class 和 namespace,这俩都可以用来表示“模块”,但是好像 C++ 程序员普遍不喜欢定义太多 namespace,class 倒是很常见,class 配合 template 也有不错的组合性。但是 C++ 还是依赖于头文件,最近才有新的 module 功能。注意标准文档的说法是程序放在“source files”里面,source files 预处理之后变成“translation unit”,同时还说明这个“source file”的名字只是 conceptual 的,不一定真在“files”里面。
Rust 中的模块有点像 C++ namespace 和 Python/Go 模块的混合体——你可以使用文件或文件夹来定义模块,也可以直接在文件内定义新的模块。一般项目里貌似前者用的较多。
Java 的核心结构是类,一个文件只允许定义一个类或接口,文件夹结构决定如何引用这个类和接口。但是这其实不是一定的,因为这个规则只是实现上 javac 编译器和 JVM 内置 ClassLoader 的默认行为(可以和 C 进行比较的是,在标准层面限定的其实是 compilation unit 和 top level declaration,具体行为是实现决定的,文件系统只是其中一个情况),JVM 实际可用的最内层接口是单个 class 的字节码而不是文件,理论上你可以用你自己的 ClassLoader 甚至自定义的 .class 文件格式规避掉默认规则。另外在我们讨论的这些语言中,Java 属于最依赖于 IDE 的那一类,这也减轻了默认规则的影响。
JavaScript 的设计背景和 C 完全不同,但是同样导向了没有“模块”结构的结果。然而 JavaScript 的函数和对象有强大的抽象和组合能力,直接就可以拿来做“模块”。于是后来就出现了一些比较通用的模块实现,现代 JavaScript 最后有了一套标准的模块,和 Java 一样,这个模块也可以自定义 loader 。最后实现上很多都是把“模块”和“文件”或“文件夹”对应起来的。

这么一圈下来,很显然的就是很多语言及语言实现,跨 domain 把编程语言中的结构,和操作系统中的文件系统嫁接到一块了。文件系统有意思的地方主要是,在现代 OS 中,文件系统是唯一指定的持久化存储抽象。用户或者程序想要存储数据,不管采用什么方式,最后基本都要落到文件系统上。

这本来没什么大问题,能直接钦定一个抽象,并且还一直很好用,只能说明这个抽象是个成功的抽象(至于所谓“UNIX 哲学”把这玩意扩大化到了整个 OS,搞得操作系统就是文件,文件就是操作系统,文件调度内存外设安全,东西南北中,文件是领导一切的这种事情另说)。但是“模块”一定要和文件系统关联起来么?再去看“模块”的定义,狭义上指编程语言结构的集合,广义上指一切可组合的编程语言结构,这和“文件”或“文件夹”并没有必然的联系。之所以实践中存在广泛的关联,可能有多方面的原因,但是主要的原因可能不难推测:如果说一切持久存储,包括程序语言结构(即“程序”)的存储都要落在文件系统上的话,就必然要设计利用文件系统存储程序的方式,很自然的,一个文件或文件夹很有可能被设计为编程语言结构的集合,而我们说的狭义模块就是编程语言结构的集合,这俩看起来是差不多的,干脆就画等号了。

这看起来很合理,但是我想再强调“文件”是文件系统的结构,“模块”是程序设计的概念或者编程语言的结构,这俩本身并没有直接的关系。所以正经的标准,比如 C/C++,Java,JavaScript 里面,都不会钦定文件系统作为模块的必要条件,而只是将其作为可能的实现之一,也就是说文件系统是个实现细节。

Python 、Go 等语言把模块和文件系统强行关联会出现一些问题,比如想要把各种语言结构放到一块(即创建一个狭义模块),必须涉及文件系统的操作,总的来说就是程序结构和文件结构高度绑定。当然有一些人可能会觉得用文件来组织模块这样更“清晰”,不过就我个人来讲,文件固然有其合理性,但是我读写程序的时候,希望的是能针对编程语言结构进行操作,而我刚才说了,文件不属于编程语言结构,绑定到一起之后在编程过程中需要在编程语言和操作系统两个 domain 中反复横跳,这就是楼主会提出这个问题的根本原因。

引入广义模块定义,并且把“文件”的概念排除掉之后就更清楚了。楼主说“with open....”“不是那个味道”,就是因为你这个时候需要跨 domain 操作文件(其实这是“文件”的正确打开方式,文件是拿来读写的不是拿来 import 的),而“是味道”的做法应该是直接操作编程语言结构,在 Python 里就是操作函数和表达式,以及它们在运行时产生的值,即:

A.py:
def phase_a(src):
...
return result

B.py:
def phase_b(src):
...
return result

main.py:
import A
import B
src_data = read_src()
m = phase_a(src_data)
n = phase_b(m)
output(n)

但是因为我们不提倡把模块和文件绑定,既然写两个文件 /狭义模块是模块化,写两个函数也是模块化,你把 phase_a 和 phase_b 两个函数(模块)全都放在 main.py 里面也无所谓。

有句话讲得好“一切都是比较而言”。C++,Rust,JavaScript 等语言,允许用户更灵活地定义狭义模块,狭义模块和文件系统不存在直接关联。Java 虽然默认实现有关联,但是因为现在默认写 Java 都要用 IDE,理论上 IDE 可以帮你解决。

另外楼主问的“中间量”,我敢保证楼主程序中的“中间量”不止 m 一个。因为当你计算 a = 1 + 2 + 4 时,1 + 2 的值 3 就是一个“中间量”,之后会继续产生另一个中间量 2 + 4 也就是 6 作为 a 的值。因为 a 本身不是程序的结果,你后面还需要用 a 进行其他的运算或者输出,所以 a 也是个中间量。上面例子的 src_data,m 和 n 都是中间量。可见这些中间量,编程语言实现已经自动帮你处理了,都很“是味道”。楼主说“A 文件就类似于一个面向过程的流水账,一步步计算出结果的这种”,这么多步,每一步都会产生“中间量”。但是楼主唯独对跨文件的“中间量”不清楚,就是因为“文件”这一个 foreign 的概念干扰了对编程语言自身概念的理解。

---

最后都说了这么多了,可以继续来看几个非常奇怪的例子:

虽然之前所讨论一些编程语言会把其“模块”结构称为“module system”,但是除 JavaScript 外,我个人不觉得其他任何一个有叫“system”的资格。Module System 的典范其实是 ML 语言,ML 中的 module 有点像 C++ 里的 template,是后加上去的,也把 ML 分成了 Core language 和 Module language 两个部分。ML 模块的最大特点是它有很强的组合性,也就是你可以像函数一样调用模块(叫做 functor ),当然和 template 一样,这种调用只能是静态的。和函数不同的是,模块允许值和类型互相组合,而函数只允许值的组合。又因为模块是唯一能把值和类型组合到一起的结构,所以 ML 的模块既有接近于函数等一等公民结构的组合性,又不存在功能的冗余。ML 有两个主要方言,Standard ML 和 OCaml,Standard ML 并没有把模块和文件系统关联起来,编译器一般只是把所有源文件合并成一个大文件编译,当然是合并要以某种拓扑序进行。OCaml 则会给每个源文件默认创建一个模块,OCaml 加入了 first-class modules,让 module 也可以在运行时动态组合,进一步增强了 module 的表达力。(需要注意 Standard ML 有标准,标准认为“we shall tacitly regard all programs as interactive”,OCaml 没有形式上的标准)

另一个是传说中的罗马正统奥斯曼(划掉)“面向对象正统”Smalltalk 。今天人们对 Smalltalk 仅有的记忆似乎主要是来自它的罗马 ... 面向对象正统宣称,但是 Smalltalk 的创新远不止于此。Smalltalk 经常被忽略的一点是,它将程序和“环境”连接在了一起,因为 Smalltalk 是 Alan Kay 和 Xerox PARC 在让计算机和编程变得更友好的过程中的尝试,所以它不使用“文件”的抽象,你的计算机或者操作系统(至少在 UI 层面),以及编程环境对你来说都是同一个“Smalltalk 环境”,这个环境中你写程序不是先创建一个“文件”,然后再写代码,而是直接创建一个类或方法。读程序也不是打开“文件”看,因为用的不是“文件浏览器”,而是一个“System Browser”( Smalltalk 环境里面有个“File Browser”,不过你编程的时候是不会去碰它的),你在这里面直接点类或者方法看。因为代码直接可以在“环境”中运行,和程序及数据交互等于和环境交互,它也很适合 Live Coding 。总的来说 Smalltalk 更多地做到了“针对编程语言结构进行操作”以及“文件系统作为实现细节存在”。
Smalltalk 中持久化也和文件没有直接关系,刚才说你的电脑就是 Smalltalk 环境,Smalltalk 是把整个环境存储成一个“Image”,关机(或者进程退出之后)再打开会加载 Image,类还是那些类,方法还是那些方法,对象还是那些对象。

这俩东西都有几十年历史了,今天倒也不是完全没有继承者,可以简单找到几个猴版:
JavaScript 等动态性较强的语言,可以直接用动态特性实现 ML 模块的组合性。
现代 IDE 都有个 Class View,可以看当前文件,或当前项目下按照程序语言结构( namespace 、类、函数、变量等)组织的程序结构。不过貌似至少 VS 里面的 Class View 只能看,不能直接创建类(因为 VS 还是没脱离文件抽象)。VS 和 Xcode 都可以创建某种“文件夹”,这个文件夹仅仅是一个逻辑的集合,仅存在于 IDE 项目文件中,不对应文件系统中的文件夹。
RDBMS 的存储过程不直接对应文件系统,而是存储在数据库里面。关系数据库本身又是一个持久存储的抽象,实际上 Smalltalk 的 Image 就是个数据库。(“文件”的问题也就在于文件系统可以被当成数据库,但是这个数据库又不够强)
Java 在标准层面主要关心类和包这种抽象的概念,而在实现层面又直接让类、包与文件系统绑定,但是单个“文件”不是“结构的集合”,而只能有一个结构,并且用 IDE 工具进一步抽象。也可以算是奥斯曼,即 Smalltalk 的继承者之一了。

另外,Smalltalk 这个 Image 机制和现在主流编程语言实现的区别,其实就是 Smalltalk 给 VM 进程状态做了持久化而已,楼上有人说这个在现代的 VM 中并不难实现。这里又涉及到另一个操作系统概念“进程”,进程有自己的内存空间,一般就代表了一个进程在某一时刻的状态(嘛,也可以叫“中间量”),楼主说的“变量框全局地储存变量”,以及回复有人说的全局作用域,都属于进程的状态。但是如果程序自己不做持久化,这个状态在进程退出之后就没了。存储分临时存储和持久存储,文件主要解决的是持久存储的问题(当然也可以做临时存储),临时存储主要是进程内部的事情。所以楼主如果没有持久存储的需求是不需要读写文件的。Smalltalk 环境把文件和进程都给你抽象掉了,你可以说它和传统操作系统做了比较全面的脱钩。

如果真的让“模块”,或者更一般地,程序的组织形式完全和“文件”脱钩,也会出现其他的问题,比如你就只能用对应语言钦定的环境和编辑器,Vim 和 Emacs 基本废了,我虽然只有三十岁,可以却有五十年的 Vim 配置经验啊!(我觉得 Emacs 还能救一救)版本管理也是个问题,理想上也是按照编程语言结构来进行管理,而不是按照文件来管理,这样就又会有只能使用语言专用工具的问题。Smalltalk 社区应该有自己的办法,我不是用户不太清楚。不过这些问题会出现的前提是现有工具本身就是按照主流编程语言和文件系统之间关系的假设做的,还是个历史包袱问题。
2021-10-16 21:41:07 +08:00
回复了 jdz 创建的主题 C++ 想买好的 cpu 来做编译用,但是编译又是偶尔才编译一次,纠结
E5 最后一代产品 v4 顶配貌似是 22c ...
如果是 Scalable 的话,我一个朋友很好奇“2100 的双路 6152”是哪里买的 ...
2021-10-16 21:34:45 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
***本回复不是给 #28 #30 #33 #34 #36 #37 #40 #41 看的,而是给通过 V 站站内通知、Google 等不同渠道来的有缘人看的***

不是刻意要“抠字眼”,解释下我为啥以 2 作为普遍的“级别”的粗略定义:

考虑工资 6k 和工资 8k 的两个人,可以勉强说这两个人的工资是一个级别。工资 6k 和工资 60k 就明显不是了。10 是一般意义上的“数量级”单位。但是在很多情况下这个单位都太大了。工资 12k 相对于工资 6k,已经是“一个级别”的边缘了。同样的规律可以应用在价格上。
孙子兵法更有意思“十则围之,五则攻之,倍则分之,敌则能战之,少则能逃之,不若则能避之”,,看起来其对于军队数量级差距的定义在 2-5 倍之间,而 10x 在这套标准中已经是顶级的碾压了。
2 对于程序员来说是比较习惯的选择,我认为也是比较合适的选择。某些时候可能显得有点小,不过无非就是原来的一个数量级变成了现在的 3.3 个数量级。
意大利有 60M 人口,尼德兰有 17M,这俩也就 3.5 倍的关系,但无论如何不能说是在一个级别上。
我现在用的显示器是 2560 × 1440 的分辨率,相对于 1920 × 1080 的屏幕是 1.78 倍像素点,而 1920 × 1080 相对于 1280 × 720 是 2.25 倍的像素点,两个比率都在 2 倍“左右”。实际用过的大概知道,这三种分辨率用起来完全不是一个级别的体验。
一般做性能优化,除去明显的低级错误后,各种 trick 提升的天花板也就是 2x,再往上就要靠并行和程序本身算法优化。但是不考虑实现方式和 KPI 好不好看,作为用户来讲,大量数据占用存储减半,复杂页面加载时间减半,能耗减半,模拟迭代速度速度翻倍,多媒体处理或游戏帧数翻倍,数据传输速率翻倍,这些都是能明显感觉到的东西——实际上对于很多比较成熟的系统做到这些都非常不容易,但是如果做到了,使用体验是超出一个级别的提升。所以我对性能问题的“数量级”定义是低于 2x 的。
2021-10-16 20:53:50 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5

> 起码 5 倍差距以上吧,不然 O(n)和 O(2n)都不算一个级别了
你都加 大 O 了常数项还有意义么 ...

> “一个级别”“基本不用”的用词含义我也不知道是几个意思

我的意思是
1. “因为后端一样,所以性能一样(或者一个级别)”的逻辑是错的
2.
> 相比 fiber 、webworker 、gpu.js 相比,wasm 考虑优先级要低
这个比“基本不用”看上去合理一些,我也觉得 WASM 一般相对于这些不是很合适。
但是我不觉得到了“基本不用”的程度。

> 底层的东西性能提升几个百分点是很有价值的,但业务层的东西就不是了

所以
3. 那也要看什么业务啊。你直接说
> 相比 fiber 、webworker 、gpu.js 相比,wasm 考虑优先级要低
不就得了,为啥非要直接一个“基本不用”扣上以至于引起
> 纯属抠字眼
> 用词含义
的误会呢。
2021-10-12 01:14:29 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5 另外我很好奇你说的“一个级别”是什么定义。
我个人对”一个级别”的定义是 2 倍。但是隔壁组的工作是用几个月甚至半年做几个百分点的提升。这远小于我定义的“一个级别“的差距,然而人家还是在做。
2021-10-12 01:08:53 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5 “少数“不等于“基本不用“。
2021-10-10 17:40:51 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5
第一我针对的是”因为后端一样,所以性能一样”的逻辑
第二,现在 Chrome 和 Firefox 都支持了 WASM 的多线程和 SIMD,楼主想做的不一定不能做。白学文章的结论是“多了一种在性能、开发成本和效果之间权衡的选择”,你这“基本不用”,我也不好说😅
2021-10-10 13:52:34 +08:00
回复了 weiwenhao 创建的主题 Google google 搜中文太毒了吧,是不是已经放弃中文搜索了
我用同一个帐号,在 iPad 上需要翻两三页才看到大量这个站群的结果,在 PC 上一突开就满脸都是 ... 看起来 Google 在这种奇怪的地方下了不少功夫
另外对于用户来讲,这个最好的手段应该是在服务器端把结果过滤掉,就不会出现“过滤之后首页根本没有结果”的现象了,不过 Google 貌似没有直接提供一个域名的全局黑名单。最骚的是这个站对“-知识网 -百科网”的关键词完全免疫,观察之后,用“-上一篇”把它干掉了(明明“百科网”在标题和正文里面都有,可能 Google 给文本分了词?虽然其实中文不需要这么干)。不过这个关键词居然貌似没有误伤 CNBlogs 。

对于这个关键词(“c 语言二进制输出 -上一篇”)来讲,我的搜索结果中唯一值得关注的只有 CNBlogs,其实还有 CSDN 和百度知道的不少结果,但是这种结果会不会仔细去看就是个人喜好了。不过我最希望 Google 给出的其实是这个:
https://zhihu.com/question/438434182/answer/1699713812 C 语言中为什么没有直接提供二进制数的输出? - 暮无井见铃的回答 - 知乎 看到 jajuju 的这个答案,我这个实验就算没白做。
可惜在 Google 给出的一共不到一百个结果中,并没有这个结果(只有个知乎专栏的),好消息是如果你 site:zhihu.com 的话还是能找到的,我就是这么找到的。可见结果权重排序基本是乱的。

从实际的角度来说,要想最大限度地“净化”搜索结果,那么应该可以搞一个白名单,Google Custom Search 貌似是可以这么做的,把 reddit.com wikipedia.org zhihu.com bilibili.com youtube.com bbs.nga.cn v2ex.com chiphell.com 等一干网站加进去就行了。这几个差不多 cover 了我需要的简体中文内容的一大半,另外还有百度贴吧,百度的结果明显比 Google 要好,这个没救。
(我注意到 Google 还喜欢:对于同一个域名的结果,只显示他认为最 relevant 的,剩下的不给你看,或者有但是在二级页面里而不是全给你直接列出来。如果中文网络内容确实如此中心化的话,在 Google 这种策略下域名白名单反倒是更优的选择)
这么搞有一个直接的缺点就是干掉了所有的独立博客,这个是没法穷举的。但是我发现好像就算不加任何过滤,中文独立博客的结果也很有限,所以直接大网站白名单好像也不会差太多。
一个典型的例子是,刚才那个关键词几乎没有来自 github.io 的结果。但是单独 site:github.io 的话可以发现并不是没有有用的结果(虽然不多,大概是被“-上一篇”给干掉了)。鉴于确实有不少人往 github.io 上放博客,所以独立博客作者可以考虑往 github.io 上扔个副本然后链到主站,这样不需要审查,也算是间接 SEO 。
2021-10-10 05:51:35 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
那么有没有正经的,“WASM 与所谓‘写的好一点’的 js”之间的对比呢?
www.sable.mcgill.ca/publications/techreports/2018-2/techrep.pdf WebAssembly and JavaScript Challenge: Numerical program performance using modern browser technologies and devices 这篇文章对比了同一套数值计算 benchmark 在 C 、WebAssembly 和 JavaScript 等不同平台下的性能,结果性能提升普遍在 1.5 到 2 倍之间。
当然,可以 argue 这个 benchmark 里面的 JS 并不是“最优化”的 JS (这个 JS 代码是他们重写过的,用了 Typed Array 之类的优化),真正“最优化”能做到什么程度呢?

这就要说回刚才那个答案了,答案回复里引用了一篇文章,干货比答案本身那是多得多: https://zhuanlan.zhihu.com/p/102692865 一个白学家眼里的 WebAssembly (以下称为“白学文章”,因为实在是浓度过高 ...)
这个文章声称:“只要你懂得如何让 JS 引擎走在 Happy Path 上,那么在浏览器里,JS 就敢和 Rust 五五开。”
这里有个前提“JS 引擎走在 Happy Path 上”,这个的难度我前面说过。另外,在这篇文章的上下文中,Rust to WebAssembly 貌似无论是在文件大小、工具链完善程度和运行时性能还是上都没有 C to WebAssembly 更好,也就是换 C 可能就是四六开了 :)
精彩的是这个 claim 后面的证据,这是来自 Firefox 和 V8 两家浏览器引擎官方的内卷成果:

https://hacks.mozilla.org/2018/01/oxidizing-source-maps-with-rust-and-webassembly Oxidizing Source Maps with Rust and WebAssembly 这个作者 Nick Fitzgerald 应该是专门搞 Rust 和 WASM 的,立场应该有一定偏向。这篇文章是讲通过把 source-map 这个包里的 hot code 改成 使用 Rust 编译成的 WebAssembly 实现,在作者定义的一组 benchmark 上实现了成倍的性能提升。文章不短,不过主要是介绍 source-map 这个库,介绍 WASM 以及怎么用,怎么做 WASM 和 JS 之间的 binding,以及 benchmark 的结果。
前 V8 开发者 Vyacheslav Egorov 看了之后就写了一篇 https://mrale.ph/blog/2018/02/03/maybe-you-dont-need-rust-to-speed-up-your-js.html Maybe you don't need Rust and WASM to speed up your JS,他声称自己不站队,只是对优化感兴趣。这篇文章同样不短,全文大部分都是关于他怎么做的优化,包括:
1. 通过使用一个“*almost* default x64.release build of the V8”——也就是他重新 build 了一个 V8 来方便自己做分析,对 V8 引擎进行 profile,发现函数调用的时候 arity 不匹配或导致性能损失,改掉之后提升了一些性能。
2. 通过手动进行 monomorphisation,提升了更多的性能。
3. 通过分析发现原来包里中加入的 cache 并没有必要,删掉之后又提升了一截性能。
4. 把原来包里的内置排序算法改成桶排序,又是一截。类似的还有去除不必要的排序,并把快排换成插入排序。
5. 通过手动模拟静态类型语言的内存布局,优化 GC 。
6. 使用 Typed Array 继续优化(这个貌似需要重写整个逻辑,作者没有真的去重写只是预测了一下性能)。
做完这一切之后——V8 下性能差不多是刚才的 WASM 版本的 1.3-1.6 倍,而 SpiderMonkey ( Firefox 的 JS 引擎)下面的性能差距要小一点。
注意这六个主要优化中,1 是 V8-specific 的,这需要你了解 V8 的实现细节,3 和 4 是对程序算法本身的优化,和使用什么语言没有关系。2,5,6 则是 C/C++/Rust 等静态类型语言的语言特性自身就有的,而为了在 JS 中“模拟”这些特性,你需要这么写代码: https://mrale.ph/blog/2018/02/03/maybe-you-dont-need-rust-to-speed-up-your-js.html#optimizing-parsing---reducing-gc-pressure
之后 Nick Fitzgerald 又单独发了一篇文章: https://fitzgeraldnick.com/2018/02/26/speed-without-wizardry.html Speed Without Wizardry 先吹了一下 Vyacheslav 的 expertise 和文章本身的质量,然后解释了下啥叫“wizardry”,最后把上面的 3 和 4 也在 WASM 版里面实现了,最后结果就是那个白学文章里面那个图:JS 和 WASM 在 V8 上完全打平,而在 SpiderMonkey 上,WASM 依然有明显的性能优势。

Nick 最后那篇文章有点阴阳怪气的意思,简单来说:但是,古尔丹,代价是什么呢?
标题的意思就是 Vyacheslav 的优化有一半属于“wizardry”,而使用 WASM 则完全不需要。他还特地摘出了 GC 优化那段,用 Vyacheslav 自己的话(“verbose and error prone code”)嘲讽了一番,然后大大的字体把 Vyacheslav 的代码 “memory[mappingA + 3] - memory[mappingB + 3]” “memory[mappingA + 4] - memory[mappingB + 4]” ... 贴出来。
(当然,他没有强调这代码之所以写成这德行,部分原因是照顾 SpiderMonkey 优化的一些短板)
他最后说:
> But a distinction between JavaScript and Rust+WebAssembly emerges when we consider the effort required to *attain* inlining and monomorphization, or to avoid allocations. Rust lets us explicitly state our desires to the compiler, we can rely on the optimizations occurring, and Rust’s natural idioms guide us towards fast code, so we don’t have to be performance wizards to get fast code. In JavaScript, on the other hand, we must communicate with oblique incantations to match each JavaScript engine’s JIT’s heuristics.
并且他同意 Vyacheslav 说的:
> Obviously each developer and each team are free to choose between spending N rigorous hours profiling and reading and thinking about their JavaScript code, or to spend M hours rewriting their stuff in a language X.
实际上,Vyacheslav 所做的优化,已经接近于重写代码了,我不认为这是“写的好一点”的问题。
这些具体的问题,那篇白学文章并没有细讲,白学文章自己推导出一个结论,就是“WASM 主要是方便把更复杂的原生应用直接搬进 Web”。其实完全不需要,WASM 自己的官方 paper (来自各大厂商的一线实现者) dl.acm.org/doi/pdf/10.1145/3062341.3062363 Bringing the Web up to Speed with WebAssembly 就明确指出了
> The initial version of WebAssembly presented here focuses on supporting low-level code, specifically compiled from C/C++.
也就是设计上就不是让你从 JS port 过去的。之后才是
> A highly important goal is to provide access to the advanced and highly tuned garbage collectors that are built into all Web browsers, thus eliminating the main shortcoming relative to JavaScript when compiling to the Web.

而在折腾了这么半天之后,JS 版在 SpiderMonkey 上终究还是没打过 WASM 。吊诡的是哪怕 JS 版的性能,貌似 V8 也没打过 SpiderMonkey,证明 V8 并非是上天入地无所不能天下无敌的神器。
Vyacheslav 还有一些观点 Nick 并没有提:
* 他认为像上述 1 一样的 V8 的坑会越来越少。我个人对此不是很乐观,主要是在工具层面,而不是语言层面解决,依然是治标不治本的“wizardry”。
* 当然他也提到了从“language features”和“tools”层面一起解决。因为他也强调“Language and Optimizations Must Be Friends”,不是什么语言都可以随便优化的。
* 而其他的优化是“universal knowledge that spans across implementations and languages”,其中算法优化自不必说,而至于 JS 和 WASM 的对比,他没有给结论,只是用“N”和“M”一笔带过。并且强调“你都可以选择”,以及“language designers and implementors”都要加油奥利给。
* 以及“Clever Optimizations Must be Diagnosable"——对于 JS 引擎自身的 magic,浏览器并没有提供任何易用的接口,开发者基本没法知道我这个代码是不是什么地方惹 JS 引擎不爽了——应该不是每个前端开发者都会自己 build 一个 V8 的吧。这方面传统编译器反而做得要好得多,以 Clang 为例,最简单地,用户可以生成 optimization report 查看重要的优化有哪些做了哪些没做,再进一步可以查看生成的 LLVM IR 和 汇编,还可以看 IR 在每一个 pass 中的变化。GCC 和 Clang 传统编译器通常还会加入大量的 option 和 extension 方便性能优化。

这个涉及另外一个问题,就是通常 WASM 就是由某种形式的“传统编译器”生成的,这个编译器一般自身就是一个优化编译器。也就是说某种程度上讲,WASM 其实在编译器层面比 JS 更优越——它既有 V8 的婆罗门优化,又有传统编译器的刹帝利优化。之前说到 JIT 优化的不确定性,类似的问题在传统编译器中同样存在,区别是总的来说传统编译器做优化一般更容易,开发者可以访问更多优化的信息,并且实际性能受运行时情况的影响更小。总的来说 WASM 依然能够提供更好的 predictability 。

最后,在 V8 中,JS 和 WASM 都是通过 TurboFan 后端编译,但是在 SpiderMonkey 中,情况也是一样的。SpiderMonkey 的资料更少,但是其官方文档 https://firefox-source-docs.mozilla.org/js/index.html 说明,SpiderMonkey 最高级的 JS 后端,原来叫 IonMonkey,后来叫 WarpMonkey (最近改的名,其实 SpiderMonkey 这么多年已经养了一堆的猴儿了),但是 IR 还是叫 Ion MIR 和 Ion LIR 。而其最高级的 WASM 后端叫 BaldrMonkey,或 WASM-Ion,是“translates the WASM input into same MIR form that WarpMonkey uses and uses the IonBackend to optimize”。“IonBackend”具体指代什么不明,但是 IR 是一样的 IR 就基本是一样的后端。在 SpiderMonkey 的 WASM 实现源码中( https://searchfox.org/mozilla-central/source/js/src/wasm/WasmIonCompile.cpp#5824 )一些关键模块,如 js::jit::OptimizeMIR 同样在 JS 编译时使用( https://searchfox.org/mozilla-central/source/js/src/jit/Ion.cpp#1577 )。https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/spidermonkey.md 这里明确说 SpiderMonkey 中 WebAssembly 使用“IonMonkey-based tier 2 compiler”。前面提到的 WASM 的 paper,也包含了实现部分,是这么说的:
> V8 (the JavaScript engine in Google’s Chrome), SpiderMonkey (the JavaScript engine in Mozilla’s Firefox) and JavaScriptCore (the JavaScript engine in WebKit) reuse their optimizing JIT compilers to compile modules ahead-of-time before instantiation.
> The baseline JIT is designed only for fast startup while the Ion optimizing JIT is compiling the module in parallel in the background. The Ion JIT is also used by SpiderMonkey as its top tier for JavaScript.
> V8, SpiderMonkey, JavaScriptCore, and Chakra all include optimizing JITs for their top tier execution of JavaScript and reuse them for maximum peak performance of WebAssembly.
> Our experience reusing the advanced JITs from 4 different JavaScript engines has been a resounding success, allowing all engines to achieve high performance in a short time.
其实并不难理解,编译器本身差别并不大,既然已经有了一个非常不错的编译器,就没必要再写一个了。

最有意思的是把这个和之前的测试结果结合起来看:SpiderMonkey 同样给 JS 和 WASM 使用了一样的后端,然而这俩的性能确实差出了一截,可见一个语言的性能和编译器后端是啥根本就没有必然联系。
2021-10-10 05:51:19 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5
这个答案说的是“如果你的应用不是非常计算密集”,“加上 js 和 wasm 之间通信的额外消耗”,并且不“使用其他的高级 的 wasm 特性”,#28 直接就说“wasm 其实是最没用的”是不是太快了点?

另外,“chrome 里 wasm 的和 js jit 用的同一个后端”其实是很正常并且意料之中的事情,但是“因为‘chrome 里 wasm 的和 js jit 用的同一个后端’,所以‘js 写的好一点和 wasm 的运行效率是一个级别的’”这个逻辑是不通的,这一点原来的答案犯了错误。我理解原答主是想 debunk WASM 的 myth,但是这个部分是纯粹的 misinformation 。总体来说答案本身质量一般(不仅存在逻辑错误,也缺乏足够的数据和细节),但是里面的链接好东西不少,这个等会细嗦。

首先,性能是个非常复杂的问题,而编译器只是其中的一环,不能简单以粗暴的编译器比较来确定性能。几个例子:
* Rust,Julia 的官方实现都使用 LLVM 作为后端,而 Haskell 也有个 LLVM 后端,那么能说 Rust,Julia 和 Haskell (使用 LLVM 后端)的性能是一样的么?更直接的一个例子是,Safari 的 JS 引擎 JavaScriptCore ( JSC ),以前有个 FTL 模块,就是把 JSC 接入 LLVM 后端,那么能说 JavaScript (在 JSC 引擎上)和 Rust 的性能是一样的么?
* GCC 和 Clang 使用了完全不同的后端实现( GCC 是自己写的后端,Clang 也是 LLVM ),然而总体性能却差不多。
* 编译器本身就是个非常复杂的问题,就算限定到 WebAssembly + LLVM 的组合,https://00f.net/2021/02/22/webassembly-runtimes-benchmarks#battle-of-the-llvms 这里有一个不同 WASM runtime 的 benchmark,其中的“Battle of the LLVMs”一节展示了若干个不同的基于 LLVM 的 runtime 的测试,可见同样是 LLVM 后端,性能也存在不小的差异。

“因为编译器后端一样,所以性能一样”这个逻辑的问题是,如果从编译器的角度考虑性能问题的话,简单来说分三个部分,第一个是给编译器的输入,第二个是编译器本身,在传统编译器中,这两个唯一确定了编译器的输出( JIT 编译器需要加上一些运行时数据,不过简单起见可以认为已经跑到了最优化的程度),第三个是编译器的目标(目标硬件,目标语言+编译器的组合,或目标 VM )。
对于 V8 引擎这个问题来说,编译器本身不变,编译器目标无法控制,所以主要就在输入上。也就是要想做到性能一样,就得做到给编译器的输入一样。而做到这一点很难,最根本原因是 JavaScript 本身并不是适合写高性能应用的语言,WASM 则更多地考虑了性能。
举个例子:很多编程语言的 primitive value 涉及到了 boxing,而 boxing 会影响性能。编译器通过优化,可能能把很多 boxing 优化掉,从而在这一方面做到和没有 boxing 的语言类似的性能,但是第一编译器不保证一定有 boxing 的优化,第二就算编译器有 boxing 优化,也不保证一定能把每个 boxing 优化掉。而如果一开始就使用没有 boxing 的语言,就根本不存在这一问题,一定是保证没有 boxing 发生的。
这只是一个简单的例子。很多语言,特别是 Tracing GC 语言中,类似的情况很多。而我刚才说过编译器是个非常复杂的东西,有可能有哪个条件没有满足,优化就不会发生。现在人类的高质量 JIT 优化编译器,比如 V8,可能确实能优化掉大部分的 boxing,但是只要 hot code 中恰好有一个地方没有优化掉,性能就下去了。而就算简单的情况能够优化,复杂的情况可能也难以优化。(另外,编译器优化复杂性的来源之一是,一个优化经常会依赖于之前的优化,也就是“前面的优化为后面的优化创造更多的机会”,而一步错了就意味着后面的相关优化都会受到影响)
V8 在优化 JS 方面确实很强,但这也意味着它优化 WASM 并不会更差,也就是 WASM 至少能提供更强的保证。WASM 给编译器的输入通常是比 JS 更优质的输入。更多需要考虑的是 JS 和 WASM 交互的 overhead 之类的问题。

说白了,没人瞧不起 V8,瞧不起的是,并且只是 JavaScript 罢了。
这也是编译器开发者的无奈——编译器开发者对自己的产品有完全的自由,然而对其输入和输出却缺乏控制力,输入是语言标准定好的,输出是硬件 ISA 标准定好的,夹在中间两头受罪。
不过现在问题并不这么简单,因为很多编程语言标准委员会的成员,自己就是该语言编译器的开发者,也就是说*某些*编译器开发者是有能力影响编程语言标准的。同样地,通过某些渠道(比如处于同一公司,或者通过各种公开或私下的反馈),编译器开发者也可以影响硬件,虽然流程可能会复杂很多?这一定程度上改变了编译器尴尬的定位,但是没有改变编译器本身的性能高度依赖于输入和输出语言的表达能力的根本逻辑——编译器开发者为了提高编译器性能而试图改变编程语言和硬件的设计这一现象,反而恰恰证明了该逻辑的正确性。
一个典型例子是 ES5 的 use strict,这里 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode 提到了该功能“Fixes mistakes that make it difficult for JavaScript engines to perform optimizations”,ES5 fix 了一些,不代表 ES5 没 fix 掉的就不需要 fix 了。
更具体的我在 https://v2ex.com/t/632869#r_8401400 讨论过一些。

当然,上面只是纸上谈兵,没有实际的数据,这里有几个例子:

首先是 WASM 的前身,asm.js 和 WASM 的对比:
www.usenix.org/system/files/atc19-jangda.pdf Not So Fast: Analyzing the Performance of WebAssembly vs. Native Code 这篇文章把 SPEC CPU benchmark,使用 Emscripten 分别编译到 asm.js 和 WASM,在 Chrome 和 Firefox 两个浏览器上跑,同时结合 native 运行测试进行对比,结论是 WASM 版本的运行时间平均是 native 的 1.5-2 倍,而 asm.js 版本的运行时间平均是 WASM 的 1.3-1.5 倍。
pspdfkit.com/blog/2018/a-real-world-webassembly-benchmark A Real-World WebAssembly Benchmark 这个则是把 PSPDFKit 这个 native 库,编译到 WebAssembly 和 asm.js (这个貌似有一定 impact,各大浏览器后来都针对这玩意做了一定优化,V8 更是直接在 blog 中引用了这个 benchmark ),然后再做 benchmark,这个可以直接在线跑。这个 workload 的结果貌似是,直到目前为止,Chrome 上 WebAssembly 和 asm.js 之间的性能并没有拉开明显差距。

有意思的也就在这:注意这两个例子都是 asm.js 和 WASM 的对比,这两者本质上其实都是差不多的东西(就是作为其他语言的目标 IR )。这并非我们想要看到的,WASM 与所谓“写的好一点”的 JS (即大多数 JavaScript 原生程序所实际使用的语言)的对比——asm.js 虽然看上去是 JS,但是压根就不是人写的 JS,而是精简掉 JS 中大量对性能不友好的特性之后的子集。特别是 PSPDFKit 这个狗东西,它占了 “iswebassemblyfastyet.com”,并且 claim 自己是在 WebAssembly,和 “JavaScript version of PSPDFKit”之间做对比。这对于我们要讨论的问题来说是有些 misleading 的。
但是,从 SPEC CPU 的结果中可以发现,哪怕是类似的两个东西(有理由相信 V8 中 WebAssembly 和 asm.js 用的也是“同一个后端”),性能同样可以有不小的差别,这就是因为编译器的输入不一样。
还真不一定有搞头,Wine 现在做得很不错,把 Wine 不能用的排除掉之后有可能就不会有“长时间使用”虚拟机的情况了。
真不一定小,模板一 monomorphize 教你做人

顺便可以看看这个 https://www.youtube.com/watch?v=b0zxIfJJLAY
2021-09-28 22:40:09 +08:00
回复了 James369 创建的主题 程序员 请教一个 CPU 密集计算多线程优化办法?
@mxT52CRuqR6o5
> 因为 chrome 里 wasm 的和 js jit 用的同一个后端
来源请求
2021-09-28 22:23:02 +08:00
回复了 kop1989 创建的主题 Java 这样的重载写法是如何通过编译的?
有点像 C++ 的 name mangling
2021-09-19 21:23:30 +08:00
回复了 gdgoldlion 创建的主题 iPhone 库克不愧是供应链管理大师, A15 一鱼三吃
楼主不如跟这位老兄认识一下:
https://v2ex.com/t/801641 仔细想了想,所谓 "尊贵会员" 独享功能,不过是开发好的接口拒绝普通用户使用
2021-09-17 19:32:16 +08:00
回复了 bagheer 创建的主题 互联网 今天,微信等 app 要支持合法外链了!
中转我倒觉得没啥,我记得 Steam 和 Telegram 都有中转页,如果能从根本不让用进化到中转,至少算是和国际接轨了。
2021-09-17 19:25:16 +08:00
回复了 paranoiddemon 创建的主题 程序员 编译原理大家是怎么学习的?
实际上大多数常用的语言实现前端用的都是手写递归下降。原因无非就是方便 hack,灵活。
不过也有例外,比如 OCaml 和 Haskell 貌似都是用的自家的 generator 。
自己玩可以用 parser combinator 。所以你把这块绕过去也无所谓,后面有的你看。

编译方面最牛逼的书私以为是 Optimizing Compilers for Modern Architectures,这书能让你了解最牛逼的那些编译器(传统意义上)都在做些什么。
2021-09-16 20:01:52 +08:00
回复了 salmon5 创建的主题 Java Oracle JDK 并没有“真”免费
如果我到时间升级到新版 JDK 还需要收费么?
如果不需要的话那就有逼人升级的成分了,这样我巴不得所有 JDK 全都这么干 :)
1 ... 28  29  30  31  32  33  34  35  36  37 ... 121  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2595 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 40ms · UTC 06:24 · PVG 14:24 · LAX 23:24 · JFK 02:24
Developed with CodeLauncher
♥ Do have faith in what you're doing.