2018年6月18日星期一

每日文章精选 2018 06 18


闭包、对象,以及堆“族”


Linuxeden 开源社区 --

在上篇文章中我们提到了闭包、对象、以及栈外的其它东西。我们学习的大部分内容都是与特定编程语言无关的元素,但是,我主要还是专注于 JavaScript,以及一些 C。让我们以一个简单的 C 程序开始,它的功能是读取一首歌曲和乐队名字,然后将它们输出给用户:

  1. #include <stdio.h>
  2. #include <string.h>
  3. char *read()
  4. {
  5. char data[64];
  6. fgets(data, 64, stdin);
  7. return data;
  8. }
  9. int main(int argc, char *argv[])
  10. {
  11. char *song, *band;
  12. puts("Enter song, then band:");
  13. song = read();
  14. band = read();
  15. printf("\n%sby %s", song, band);
  16. return 0;
  17. }

stackFolly.c 下载

如果你运行这个程序,你会得到什么?(=> 表示程序输出):

  1. ./stackFolly
  2. => Enter song, then band:
  3. The Past is a Grotesque Animal
  4. of Montreal
  5. => ?ǿontreal
  6. => by ?ǿontreal

(曾经的 C 新手说)发生了错误?

事实证明,函数的栈变量的内容仅在栈帧活动期间才是可用的,也就是说,仅在函数返回之前。在上面的返回中,被栈帧使用过的内存 被认为是可用的 ,并且在下一个函数调用中可以被覆写。

下面的图展示了这种情况下究竟发生了什么。这个图现在有一个图片映射(LCTT 译注:译文中无法包含此映射,上下两个矩形区域分别链接至输出的 #47 行和 #70 行),因此,你可以点击一个数据片断去看一下相关的 GDB 输出(GDB 命令在 这里)。只要 read() 读取了歌曲的名字,栈将是这个样子:

在这个时候,这个 song 变量立即指向到歌曲的名字。不幸的是,存储字符串的内存位置准备被下次调用的任意函数的栈帧重用。在这种情况下,read() 再次被调用,而且使用的是同一个位置的栈帧,因此,结果变成下图的样子(LCTT 译注:上下两个矩形映射分别链接至 #76 行和 #79 行):

乐队名字被读入到相同的内存位置,并且覆盖了前面存储的歌曲名字。bandsong 最终都准确指向到相同点。最后,我们甚至都不能得到 “of Montreal”(LCTT 译注:一个欧美乐队的名字)的正确输出。你能猜到是为什么吗?

因此,即使栈很有用,但也有很重要的限制。它不能被一个函数用于去存储比该函数的运行周期还要长的数据。你必须将它交给 ,然后与热点缓存、明确的瞬时操作、以及频繁计算的偏移等内容道别。有利的一面是,它是 工作 的:

这个代价是你必须记得去 free() 内存,或者由一个垃圾回收机制花费一些性能来随机回收,垃圾回收将去找到未使用的堆对象,然后去回收它们。那就是栈和堆之间在本质上的权衡:性能 vs. 灵活性。

大多数编程语言的虚拟机都有一个中间层用来做一个 C 程序员该做的一些事情。栈被用于 值类型 ,比如,整数、浮点数、以及布尔型。这些都按特定值(像上面的 argc)的字节顺序被直接保存在本地变量和对象字段中。相比之下,堆被用于 引用类型 ,比如,字符串和 对象 。 变量和字段包含一个引用到这个对象的内存地址,像上面的 songband

参考这个 JavaScript 函数:

  1. function fn()
  2. {
  3. var a = 10;
  4. var b = { name: 'foo', n: 10 };
  5. }

它可能的结果如下(LCTT 译注:图片内“object”、“string”和“a”的映射分别链接至 #1671 行、 #8656 行和 #1264 行):

我之所以说“可能”的原因是,特定的行为高度依赖于实现。这篇文章使用的许多流程图形是以一个 V8 为中心的方法,这些图形都链接到相关的源代码。在 V8 中,仅 小整数以值的方式保存 。因此,从现在开始,我将在对象中直接以字符串去展示,以避免引起混乱,但是,请记住,正如上图所示的那样,它们在堆中是分开保存的。

现在,我们来看一下闭包,它其实很简单,但是由于我们将它宣传的过于夸张,以致于有点神化了。先看一个简单的 JS 函数:

  1. function add(a, b)
  2. {
  3. var c = a + b;
  4. return c;
  5. }

这个函数定义了一个 词法域 lexical scope,它是一个快乐的小王国,在这里它的名字 abc 是有明确意义的。它有两个参数和由函数声明的一个本地变量。程序也可以在别的地方使用相同的名字,但是在 add 内部它们所引用的内容是明确的。尽管词法域是一个很好的术语,它符合我们直观上的理解:毕竟,我们从字面意义上看,我们可以像词法分析器一样,把它看作在源代码中的一个文本块。

在看到栈帧的操作之后,很容易想像出这个名称的具体实现。在 add 内部,这些名字引用到函数的每个运行实例中私有的栈的位置。这种情况在一个虚拟机中经常发生。

现在,我们来嵌套两个词法域:

  1. function makeGreeter()
  2. {
  3. return function hi(name){
  4. console.log('hi, ' + name);
  5. }
  6. }
  7. var hi = makeGreeter();
  8. hi('dear reader'); // prints "hi, dear reader"

那样更有趣。函数 hi 在函数 makeGreeter 运行的时候被构建在它内部。它有它自己的词法域,name 在这个地方是一个栈上的参数,但是,它似乎也可以访问父级的词法域,它可以那样做。我们来看一下那样做的好处:

  1. function makeGreeter(greeting)
  2. {
  3. return function greet(name){
  4. console.log(greeting + ', ' + name);
  5. }
  6. }
  7. var heya = makeGreeter('HEYA');
  8. heya('dear reader'); // prints "HEYA, dear reader"

虽然有点不习惯,但是很酷。即便这样违背了我们的直觉:greeting 确实看起来像一个栈变量,这种类型应该在 makeGreeter() 返回后消失。可是因为 greet() 一直保持工作,出现了一些奇怪的事情。进入闭包(LCTT 译注:“Context” 和 “JSFunction” 映射分别链接至 #188 行和 #7245 行):

虚拟机分配一个对象去保存被里面的 greet() 使用的父级变量。它就好像是 makeGreeter 的词法作用域在那个时刻被 关闭 closed over 了,一旦需要时被具体化到一个堆对象(在这个案例中,是指返回的函数的生命周期)。因此叫做 闭包 closure,当你这样去想它的时候,它的名字就有意义了。如果使用(或者捕获)了更多的父级变量,对象内容将有更多的属性,每个捕获的变量有一个。当然,发送到 greet() 的代码知道从对象内容中去读取问候语,而不是从栈上。

这是完整的示例:

  1. function makeGreeter(greetings)
  2. {
  3. var count = 0;
  4. var greeter = {};
  5. for (var i = 0; i < greetings.length; i++) {
  6. var greeting = greetings[i];
  7. greeter[greeting] = function(name){
  8. count++;
  9. console.log(greeting + ', ' + name);
  10. }
  11. }
  12. greeter.count = function(){return count;}
  13. return greeter;
  14. }
  15. var greeter = makeGreeter(["hi", "hello","howdy"])
  16. greeter.hi('poppet');//prints "howdy, poppet"
  17. greeter.hello('darling');// prints "howdy, darling"
  18. greeter.count(); // returns 2

是的,count() 在工作,但是我们的 greeter 是在 howdy 中的栈上。你能告诉我为什么吗?我们使用 count 是一条线索:尽管词法域进入一个堆对象中被关闭,但是变量(或者对象属性)带的值仍然可能被改变。下图是我们拥有的内容(LCTT 译注:映射从左到右“Object”、“JSFunction”和“Context”分别链接至 #1671 行、#7245 行和 #188 行):

这是一个被所有函数共享的公共内容。那就是为什么 count 工作的原因。但是,greeting 也是被共享的,并且它被设置为迭代结束后的最后一个值,在这个案例中是 “howdy”。这是一个很常见的一般错误,避免它的简单方法是,引用一个函数调用,以闭包变量作为一个参数。在 CoffeeScript 中, do 命令提供了一个实现这种目的的简单方式。下面是对我们的 greeter 的一个简单的解决方案:

  1. function makeGreeter(greetings)
  2. {
  3. var count = 0;
  4. var greeter = {};
  5. greetings.forEach(function(greeting){
  6. greeter[greeting] = function(name){
  7. count++;
  8. console.log(greeting + ', ' + name);
  9. }
  10. });
  11. greeter.count = function(){return count;}
  12. return greeter;
  13. }
  14. var greeter = makeGreeter(["hi", "hello", "howdy"])
  15. greeter.hi('poppet'); // prints "hi, poppet"
  16. greeter.hello('darling'); // prints "hello, darling"
  17. greeter.count(); // returns 2

它现在是工作的,并且结果将变成下图所示(LCTT 译注:映射从左到右“Object”、“JSFunction”和“Context”分别链接至 #1671 行、#7245 行和 #188 行):

这里有许多箭头!在这里我们感兴趣的特性是:在我们的代码中,我们闭包了两个嵌套的词法内容,并且完全可以确保我们得到了两个链接到堆上的对象内容。你可以嵌套并且闭包任何词法内容,像“俄罗斯套娃”似的,并且最终从本质上说你使用的是所有那些 Context 对象的一个链表。

当然,就像受信鸽携带信息启发实现了 TCP 一样,去实现这些编程语言的特性也有很多种方法。例如,ES6 规范定义了 词法环境 作为 环境记录 ( 大致相当于在一个块内的本地标识)的组成部分,加上一个链接到外部环境的记录,这样就允许我们看到的嵌套。逻辑规则是由规范(一个希望)所确定的,但是其实现取决于将它们变成比特和字节的转换。

你也可以检查具体案例中由 V8 产生的汇编代码。 Vyacheslav Egorov 有一篇很好的文章,它使用 V8 的 闭包内部构件 详细解释了这一过程。我刚开始学习 V8,因此,欢迎指教。如果你熟悉 C#,检查闭包产生的中间代码将会很受启发 —— 你将看到显式定义的 V8 内容和实例化的模拟。

闭包是个强大的“家伙”。它在被一组函数共享期间,提供了一个简单的方式去隐藏来自调用者的信息。我喜欢它们真正地隐藏你的数据:不像对象字段,调用者并不能访问或者甚至是看到闭包变量。保持接口清晰而安全。

但是,它们并不是“银弹”(LCTT 译注:意指极为有效的解决方案,或者寄予厚望的新技术)。有时候一个对象的拥护者和一个闭包的狂热者会无休止地争论它们的优点。就像大多数的技术讨论一样,他们通常更关注的是自尊而不是真正的权衡。不管怎样,Anton van Straaten 的这篇 史诗级的公案 解决了这个问题:

德高望重的老师 Qc Na 和它的学生 Anton 一起散步。Anton 希望将老师引入到一个讨论中,Anton 说:“老师,我听说对象是一个非常好的东西,是这样的吗?Qc Na 同情地看了一眼,责备它的学生说:“可怜的孩子 —— 对象不过是穷人的闭包而已。” Anton 待它的老师走了之后,回到他的房间,专心学习闭包。他认真地阅读了完整的 “Lambda:The Ultimate…” 系列文章和它的相关资料,并使用一个基于闭包的对象系统实现了一个小的架构解释器。他学到了很多的东西,并期待告诉老师他的进步。在又一次和 Qc Na 散步时,Anton 尝试给老师留下一个好的印象,说“老师,我仔细研究了这个问题,并且,现在理解了对象真的是穷人的闭包。”Qc Na 用它的手杖打了一下 Anton 说:“你什么时候才能明白?闭包是穷人的对象。”在那个时候,Anton 顿悟了。Anton van Straaten 说:“原来架构这么酷啊?”

探秘“栈”系列文章到此结束了。后面我将计划去写一些其它的编程语言实现的主题,像对象绑定和虚表。但是,内核调用是很强大的,因此,明天将发布一篇操作系统的文章。我邀请你 订阅关注我


via:https://manybutfinite.com/post/closures-objects-heap/

作者:Gustavo Duarte 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux 中国 荣誉推出

编译自: https://manybutfinite.com/post/closures-objects-heap/ 作者: Gustavo Duarte
原创: LCTT https://linux.cn/article-9759-1.html 译者: qhwdw

https://ift.tt/2M2E9nU

京东构建了全球最大的 Kubernetes 集群,没有之一


Linuxeden 开源社区 --image

作者 鲍永成

从 2014 年开始,InfoQ 就一直在追踪京东 618 相关的技术报道,在我们的系列文章中,你肯定也能察觉到京东在技术上的成长速度。5 年之前,每逢大促,我的朋友圈中好似有很多人在看笑话,因为在高并发大流量面前,那时的京东总是有些脆弱,特别是对于淘宝 / 天猫。

几年时间,一晃而过。这次 618,我刻意搜索了下自己的朋友圈,发现基本没有人再去吐槽京东『逢促必挂』的技术笑话了,转而大家都在夸赞京东的送货速度以及服务品质。是的,当用户的目光聚焦到一家公司的业务本身时,这也证明他的技术已经相对成熟,起码足以支撑业务的发展速度。也就是说,对于京东来说,技术已经不再是业务增长的瓶颈。

我现在还清晰记得自己在 2015 年 618 当天去京东总部采访京东商城总架构师刘海锋时的情景,他当时激情澎湃地向我介绍了京东基于 Docker 容器和 OpenStack 的弹性计算云项目的种种优势,并笑着说,再过几年,随着这个项目的成熟,京东的技术人员面对大促,再也不用这么辛苦,这么紧张了。

我应该也是被他的话所感动,亦或者作为一个外行,我似乎也能理解一个技术人应有的技术信仰。所以在当时的采访文章里,我写下了这样的话:京东弹性计算云项目将深刻影响京东未来几年的基础架构。

然而,一年之后的 2016 年京东 618,当再次采访弹性云项目负责人鲍永成时,我们才明白,弹性云对于京东的真正价值和意义。永成说,这一年是弹性云在京东全面战略落地的重要一年,它也将全面提升京东的基础设施利用效率。

紧接着的 2017 年,京东的业务,以及相关的中间件已经全部迁移到了弹性云之上,并且永成表示,他们开始了新的技术升级,将会引入 Google 开源的 Kubernetes 技术来重构相关技术栈,并且自己也会为开源添砖加瓦,回馈社区。

2018 年 4 月底,我看到了京东宣布加入 CNCF 云原生计算基金会的消息。这时,我才明白,京东的 Kubernetes 集群已经是全球最大了,并且没有之一。

感慨之余,我又翻出了过去几年 InfoQ 对京东 618 的特别报道,当我站在历史的角度去审视这些采访时,我也才真正理解媒体记录的价值。我想,若干年后在去看这一系列的技术升级,你只要看这几年 InfoQ 的文章标题就足以理解变化的关键点。

  • 2015 年京东 618:Docker 扛大旗,弹性伸缩成重点
  • 2016 年京东 618:15 万个 Docker 实例,所有业务全部容器化
  • 2017 年京东 618:容器技法日趋娴熟,数个项目即将开源回馈社区

2018 年的 618,鲍永成和他的同事们写下了这篇文章,他们希望能够系统梳理过去几年京东在基础架构方面的点击探索,也希望这系列文章能够帮助到更多的中小公司少走弯路。以下为系列文章的第一篇。

不在江湖久已。这两年来我们(京东)完成了从 Kubernetes 的落地、推广,到大规模运营的历程。走过了那些山水,看过了那些风景,现在,我想,是时候讲讲我们的故事了。

故事要从四年前讲起,JDOS(Jingdong Datacenter Operating System) 1.0 于 2014 年推出,基于 OpenStack 进行了深度定制,并在国内率先将容器引入生产环境。经历了 2015 年 6.18/11.11 的考验。团队积累了大量的容器运营经验,对 Linux 内核、网络、存储等深度定制,实现了容器秒级分配。

2016 年,我们将精力集中与了完善容器的监控、网络、存储,镜像中心等容器生态建设,并积累了丰富的运营监控数据。也是在这一年,我们渐渐意识到了 OpenStack 架构的笨重,启动了新一代容器引擎平台 (JDOS 2.0) 的研发。

2017 年,JDOS2.0 全面推出,JDOS 从基础设施升华到应用平台。2.0 推进了业务从源码到编译打包构建镜像到上线的全流程,建设了一站式应用运行平台。 这一年,我们逐步完善了容器的监控、网络、存储,镜像中心等容器生态建设,开发了基于 BGP 的 Skynet 网络、ContainerLB、ContainerDNS、ContainerFS 等多个项目。

并将其中部分项目进行了开源 (https://github.com/tiglabs/)。我们为我们的容器生态起了一个全新的名字:阿基米德,意为撬动数据中心的人。

image

罗马不是一天建成的。在容器化的过程中,我们经历了很多,在这里我认为有必要回顾一下我们的经验和教训,有资于后来者借鉴与参考。

  1. 不轻易放弃用户 。如果大量的改造工作都由业务方来承担,那么容器化的推动将会是一件极其艰难的事情。许多老的业务可能没有那么无状态,甚而有一些诸如 ip 不变等的特殊需求。为用户的定制和改造,不仅是一种妥协,更是提供一个兼容老的业务方式以使其容器化的方案。如果总是拒绝用户,那么在失去用户的同时,也失去了与他们一起成长的机会。贴近用户,了解用户的需求,进行分析、归类和适应性的改造。在成就了业务的同时,也是成就了自己。
  2. 幻想一蹴而就和急功近利,都是容器化过程中的大忌 。对于应用进行改造的理想很丰满,现实却总是很骨感。因为这需要业务的大量的人力、物力、财力的支持。我们在容器化的过程中,采用了渐进式的策略。在开始,允许用户将容器以虚拟机/物理机的使用方式进行使用,由其传统的应用运维使用以往的工具进行部署和维护。同时,我们也提供了编译构建上线的一站式服务,支持滚动升级、灰度发布、负载均衡、域名解析等等服务。用便捷的运维管理方式吸引用户自主进行转变。再后来,我们放开了镜像的构建过程。已经有越来越多的用户愿意自己去学习 Dockerfile,以便构建起属于自己业务的镜像了。
  3. 对于技术上的质疑要用技术来解决 。新技术的推广难免受到质疑与挑战。要用于接受这些质疑和挑战,并用技术和数据来证明自己。当然这也对团队自身提出了更高的要求。对于新技术不是了解就可以了,而是能够吃深吃透,进而根据场景进行定制。
  4. 架构要能化繁为简,易于维护运营和排障 。社区的功能总是大而全,而对于我们具体的落地实践来说,我们更多要做的是减法,将原本繁杂的架构和流程梳理清晰,剪除不必要的功能,保证整个平台良好的可维护性。
  5. 守住稳定的底线 。稳定才是作为平台赖以生存的基础。将平台进行加固,确保其稳定,不是说说而已,这其中有大量的工作需要做。稳定不是保守,不是守住现在的版本不再研发。而是要对于整个平台的每个环节都能进行代码级的精准掌握,对于上线前功能、性能进行充分测试,对于突发状况有预案处理。从流程上能抑制雪崩问题的发生,并能够控制故障的影响范围。这些我们的具体工作将在未来的章节进行详述。

调度的意义与难点

在实现了 99.9%的业务容器化后,阿基米德迎来了全新的挑战。近几年硬件成本上涨带来的 ROI 压力以及建设异地多活的数据中心的实际需求,使得阿基米德 JDOS 的重点从容器集群管理转向了调度,JDOS 也从集群管理平台的角色逐渐转换为了调度平台。

调度的意义从微观上来说,是为每个容器寻找到一个合适的落脚点,也就是节点。但是从宏观角度来说,其意义在于将整个数据中心计算效能的提升。具体来说,调度可以在缩减资源碎片,资源时空复用,节能降耗以及提升计算效率方面取得巨大收益。而且该收益随着集群规模的扩大,会有更为明显的提升。

image

调度虽然收益颇丰,但是要实现也非轻而易举。调度不是空中楼阁,它依赖于大量的基础工作。这其中一些典型的问题,需要一一解决,比如异构、隔离、监控、评估等问题。

image

在 JDOS 1.0 中,因为容器更多的是以胖容器的形式存在,因此容器的使用相对来说是静态的,也就是一次调度完成后容器不会轻易迁移。除非节点故障、维护等特殊情况,则将其迁移到别的节点。而在 JDOS 2.0 中,情况就变得更为复杂了。JDOS 2.0 允许用户自动或者手动地进行应用的扩容缩容。并且,由于大数据、AI、serverless 等任务的引入,平台在将其与业务容器进行混合部署的同时,也需要具备对相关任务进行实时监控和异常驱逐的能力。而这些,对于调度平台的动态处理能力、时间规划能力都提出了更高的要求。

接下来,我们将用一系列的文章,深入解剖阿基米德调度平台。下一章预告《第一章:事非经过不知难,Kubernetes 的定制与优化》,欢迎关注聊聊架构微信公众号,第一时间阅读系列文章。

转自 https://ift.tt/2ljbcJk

https://ift.tt/2tgPkBK

为什么 PHP 程序员应该学习使用 Swoole


Linuxeden 开源社区 --详解 Swoole 协程为什么适合 I/O 密集型场景

最近两个月一直在研究 Swoole,研究成果即将在 6.21 正式开源发布,这段时间没有来水文章,趁着今天放假来水水吧。

借助这篇文章,我希望能够把 Swoole 安利给更多人。虽然 Swoole 可能目前定位是一些高级 phper 的玩具,让中低级望而生畏,可能对一些应用场景也一脸懵逼,但其实没这么难的。

在 Swoole 官网的自我介绍是“面向生产环境的 PHP 异步网络通信引擎”,首先 Swoole 它是一个网络应用的开发工具,它支持 Http、TCP、UDP、WebSocket。

Swoole 和我们传统的 PHP 开发差别是有的,需要理解的概念也是有的。使用目前一些基于 Swoole 的框架开发的话,从开发习惯上和传统的 TP、LV 框架相差不多。

那为什么要使用 Swoole?

宇润认为有以下几点:

  • 常驻内存,避免重复加载带来的性能损耗,提升海量性能
  • 协程异步,提高对 I/O 密集型场景并发处理能力(如:微信开发、支付、登录等)
  • 方便地开发 Http、WebSocket、TCP、UDP 等应用,可以与硬件通信
  • PHP 高性能微服务架构成为现实

常驻内存

目前传统 PHP 框架,在处理每个请求之前,都要做一遍加载框架文件、配置的操作。这可能已经成为性能问题的一大原因,而使用 Swoole 则没有这个问题,一次加载多次使用。

协程

如下图所示,这是同一个线程处理并发请求的场景,比如你某个接口中需要调用其它 api 接口或读写大文件,传统同步阻塞和协程异步的优势就体现了出来。

详解 Swoole 协程为什么适合 I/O 密集型场景

说到协程,就得先简单说说进程和线程,众所周知进程是很占用资源的,为了处理请求大量创建进程肯定是得不偿失的。而多线程应用就比较多了,在 CPU 层面有几个核心就会执行几个任务,线程一旦创建的多了,就会有线程调度的损耗。

协程是在单线程基础上实现的,它可以最大限度利用 CPU 资源,而不会在等待 I/O 时白白浪费。当然,协程数越多占用的内存也就越多,不过这个是可以接受的,相比进程和线程,占用的资源是相对较少的。

使用协程时,遇到读写文件、请求接口等场景,会自动挂起协程,把 CPU 让给其它协程执行任务,这样可以提升单线程的 CPU 资源利用率,减少浪费,从而提高性能。

协程代码示例:

<?php
use Swoole\Coroutine as co;
 
// 协程 
$time = microtime(true);
// 创建 10 个协程 
for($i = 0; $i < 10; ++$i)
{
    // 创建协程 
    go(function() use($i){
        co::sleep(1.0); // 模拟请求接口、读写文件等 I/O
        echo $i, PHP_EOL;
    });
}
swoole_event_wait();
echo 'co time:', microtime(true) - $time, ' s', PHP_EOL;
 
// 同步 
$time = microtime(true);
// 创建 10 个协程 
for($i = 0; $i < 10; ++$i)
{
    sleep(1); // 模拟请求接口、读写文件等 I/O
    echo $i, PHP_EOL;
}
echo 'sync time:', microtime(true) - $time, ' s', PHP_EOL;

运行结果:

0
9
8
7
6
5
4
3
2
1
co time:1.0087130069733 s
0
1
2
3
4
5
6
7
8
9
sync time:10.010055065155 s

从上面结果可以看出,协程方式执行并不是顺序的,性能更高,在 sleep 时会把当前线程的任务执行权交给其他协程。

创建 Http 服务

其实也没想象中的难,看代码:

$http = new swoole_http_server("127.0.0.1", 9501);
$http->on('request', function ($request, $response) {
    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});
$http->start();

微服务

Tars 是腾讯从 2008 年到今天一直在使用的后台逻辑层的统一应用框架 TAF(Total Application Framework),目前支持 C++,Java,PHP,Nodejs 语言。该框架为用户提供了涉及到开发、运维、以及测试的一整套解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。 它集可扩展协议编解码、高性能 RPC 通信框架、名字路由与发现、发布监控、日志统计、配置管理等于一体,通过它可以快速用微服务的方式构建自己的稳定可靠的分布式应用,并实现完整有效的服务治理。

详见:https://segmentfault.com/a/1190000011825769

如有错误之处欢迎指出,我是真心想向大家推荐 Swoole!

转自  https://ift.tt/2yd18ec
https://ift.tt/2Mvlm5X

2018年6月17日星期日

每日文章精选 2018 06 17


AMD 12nm 二代锐龙新品现身:Ryzen 3 2300X/5 2500X


Linuxeden 开源社区 --AMD 12nm二代锐龙新品现身:Ryzen 3 2300X/5 2500X

AMD 二代锐龙处理器的家族尚未补完,比如入门的 Ryzen 3。

AMD 12nm二代锐龙新品现身:Ryzen 3 2300X/5 2500X

但是越来越多的迹象表明,AMD 是有所准备的,只是等待一个推出时机,比如 B450 主板同样尚未正是官宣,如果让 Ryzen 3 配合 X470,显然有些“畸形”。

AMD 12nm二代锐龙新品现身:Ryzen 3 2300X/5 2500X

在 Geekbench 4 上出现了 AMD Ryzen 3 2300X/Ryzen 5 2500X,前者 4 核心设计,不支持超线程,基础主频 3.5GHz,加速可达 4.0GHz。单核 4734 分,多核 13999 分。

AMD 12nm二代锐龙新品现身:Ryzen 3 2300X/5 2500X

Ryzen 5 2500X 基础主频 3.6GHz,加速 4.0GHz,由于测试所用的是 X370,所以这个加速频率没有 XFR2 参与。

另外,Ryzen 5 2500X 是 4 核 8 线程的,单核 4782 分,多核 17291 分。

从性价比上看,Ryzen 3 2300X 的目标是 120~140 美元左右,对打 i5-7600K,Ryzen 5 2500X 的目标是 150~160 美元,对打 i7-7700,预计 7 月份会发布。

AMD 12nm二代锐龙新品现身:Ryzen 3 2300X/5 2500X

转自 https://ift.tt/2LZ8FPM

https://ift.tt/2JUkNkr

使用 OpenCV 进行高动态范围(HDR)成像


Linuxeden 开源社区 --High Dynamic Range (HDR)

在本教程中,我们将学习如何使用由不同曝光设置拍摄的多张图像创建 高动态范围 High Dynamic Range(HDR)图像。 我们将以 C++ 和 Python 两种形式分享代码。

什么是高动态范围成像?

大多数数码相机和显示器都是按照 24 位矩阵捕获或者显示彩色图像。 每个颜色通道有 8 位,因此每个通道的像素值在 0-255 范围内。 换句话说,普通的相机或者显示器的动态范围是有限的。

但是,我们周围世界动态范围极大。 在车库内关灯就会变黑,直接看着太阳就会变得非常亮。 即使不考虑这些极端,在日常情况下,8 位的通道勉强可以捕捉到现场场景。 因此,相机会尝试去评估光照并且自动设置曝光,这样图像的最关注区域就会有良好的动态范围,并且太暗和太亮的部分会被相应截取为 0 和 255。

在下图中,左侧的图像是正常曝光的图像。 请注意,由于相机决定使用拍摄主体(我的儿子)的设置,所以背景中的天空已经完全流失了,但是明亮的天空也因此被刷掉了。 右侧的图像是由 iPhone 生成的 HDR 图像。

High Dynamic Range (HDR)

High Dynamic Range (HDR)

iPhone 是如何拍摄 HDR 图像的呢? 它实际上采用三种不同的曝光度拍摄了 3 张图像,3 张图像拍摄非常迅速,在 3 张图像之间几乎没有产生位移。然后组合三幅图像来产生 HDR 图像。 我们将在下一节看到一些细节。

将在不同曝光设置下获取的相同场景的不同图像组合的过程称为高动态范围(HDR)成像。

高动态范围(HDR)成像是如何工作的?

在本节中,我们来看下使用 OpenCV 创建 HDR 图像的步骤。

要想轻松学习本教程,请点击 此处 下载 C++ 和 Python 代码还有图像。 如果您有兴趣了解更多关于人工智能,计算机视觉和机器学习的信息,请 订阅 我们的电子杂志。

第 1 步:捕获不同曝光度的多张图像

当我们使用相机拍照时,每个通道只有 8 位来表示场景的动态范围(亮度范围)。 但是,通过改变快门速度,我们可以在不同的曝光条件下拍摄多个场景图像。 大多数单反相机(SLR)有一个功能称为 自动包围式曝光 Auto Exposure Bracketing(AEB),只需按一下按钮,我们就可以在不同的曝光下拍摄多张照片。 如果你正在使用 iPhone,你可以使用这个 自动包围式 HDR 应用程序 ,如果你是一个 Android 用户,你可以尝试一个 更好的相机应用程序

场景没有变化时,在相机上使用自动包围式曝光或在手机上使用自动包围式应用程序,我们可以一张接一张地快速拍摄多张照片。 当我们在 iPhone 中使用 HDR 模式时,会拍摄三张照片。

  1. 曝光不足的图像:该图像比正确曝光的图像更暗。 目标是捕捉非常明亮的图像部分。
  2. 正确曝光的图像:这是相机将根据其估计的照明拍摄的常规图像。
  3. 曝光过度的图像:该图像比正确曝光的图像更亮。 目标是拍摄非常黑暗的图像部分。

但是,如果场景的动态范围很大,我们可以拍摄三张以上的图片来合成 HDR 图像。 在本教程中,我们将使用曝光时间为 1/30 秒,0.25 秒,2.5 秒和 15 秒的 4 张图像。 缩略图如下所示。

Auto Exposure Bracketed HDR image sequence

Auto Exposure Bracketed HDR image sequence

单反相机或手机的曝光时间和其他设置的信息通常存储在 JPEG 文件的 EXIF 元数据中。 查看此 链接 可在 Windows 和 Mac 中查看存储在 JPEG 文件中的 EXIF 元数据。 或者,您可以使用我最喜欢的名为 EXIFTOOL 的查看 EXIF 的命令行工具。

我们先从读取分配到不同曝光时间的图像开始。

C++

  1. void readImagesAndTimes(vector<Mat> &images, vector<float> &times)
  2. {
  3. int numImages = 4;
  4. // 曝光时间列表
  5. static const float timesArray[] = {1/30.0f,0.25,2.5,15.0};
  6. times.assign(timesArray, timesArray + numImages);
  7. // 图像文件名称列表
  8. static const char* filenames[] = {"img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"};
  9. for(int i=0; i < numImages; i++)
  10. {
  11. Mat im = imread(filenames[i]);
  12. images.push_back(im);
  13. }
  14. }

Python

  1. def readImagesAndTimes():
  2. # 曝光时间列表
  3. times = np.array([ 1/30.0, 0.25, 2.5, 15.0 ], dtype=np.float32)
  4. # 图像文件名称列表
  5. filenames = ["img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"]
  6. images = []
  7. for filename in filenames:
  8. im = cv2.imread(filename)
  9. images.append(im)
  10. return images, times

第 2 步:对齐图像

合成 HDR 图像时使用的图像如果未对齐可能会导致严重的伪影。 在下图中,左侧的图像是使用未对齐的图像组成的 HDR 图像,右侧的图像是使用对齐的图像的图像。 通过放大图像的一部分(使用红色圆圈显示的)我们会在左侧图像中看到严重的鬼影。

Misalignment problem in HDR

Misalignment problem in HDR

在拍摄照片制作 HDR 图像时,专业摄影师自然是将相机安装在三脚架上。 他们还使用称为 镜像锁定 功能来减少额外的振动。 即使如此,图像可能仍然没有完美对齐,因为没有办法保证无振动的环境。 使用手持相机或手机拍摄图像时,对齐问题会变得更糟。

幸运的是,OpenCV 提供了一种简单的方法,使用 AlignMTB 对齐这些图像。 该算法将所有图像转换为 中值阈值位图 median threshold bitmaps(MTB)。 图像的 MTB 生成方式为将比中值亮度的更亮的分配为 1,其余为 0。 MTB 不随曝光时间的改变而改变。 因此不需要我们指定曝光时间就可以对齐 MTB。

基于 MTB 的对齐方式的代码如下。

C++

  1. // 对齐输入图像
  2. Ptr<AlignMTB> alignMTB = createAlignMTB();
  3. alignMTB->process(images, images);

Python

  1. # 对齐输入图像
  2. alignMTB = cv2.createAlignMTB()
  3. alignMTB.process(images, images)

第 3 步:提取相机响应函数

典型相机的响应与场景亮度不成线性关系。 那是什么意思呢? 假设有两个物体由同一个相机拍摄,在现实世界中其中一个物体是另一个物体亮度的两倍。 当您测量照片中两个物体的像素亮度时,较亮物体的像素值将不会是较暗物体的两倍。 在不估计 相机响应函数 Camera Response Function(CRF)的情况下,我们将无法将图像合并到一个 HDR 图像中。

将多个曝光图像合并为 HDR 图像意味着什么?

只考虑图像的某个位置 (x,y) 一个像素。 如果 CRF 是线性的,则像素值将直接与曝光时间成比例,除非像素在特定图像中太暗(即接近 0)或太亮(即接近 255)。 我们可以过滤出这些不好的像素(太暗或太亮),并且将像素值除以曝光时间来估计像素的亮度,然后在像素不差的(太暗或太亮)所有图像上对亮度值取平均。我们可以对所有像素进行这样的处理,并通过对“好”像素进行平均来获得所有像素的单张图像。

但是 CRF 不是线性的, 我们需要评估 CRF 把图像强度变成线性,然后才能合并或者平均它们。

好消息是,如果我们知道每个图像的曝光时间,则可以从图像估计 CRF。 与计算机视觉中的许多问题一样,找到 CRF 的问题本质是一个最优解问题,其目标是使由数据项和平滑项组成的目标函数最小化。 这些问题通常会降维到线性最小二乘问题,这些问题可以使用 奇异值分解 Singular Value Decomposition(SVD)来解决,奇异值分解是所有线性代数包的一部分。 CRF 提取算法的细节在 从照片提取高动态范围辐射图 这篇论文中可以找到。

使用 OpenCV 的 CalibrateDebevec 或者 CalibrateRobertson 就可以用 2 行代码找到 CRF。本篇教程中我们使用 CalibrateDebevec

C++

  1. // 获取图像响应函数 (CRF)
  2. Mat responseDebevec;
  3. Ptr<CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
  4. calibrateDebevec->process(images, responseDebevec, times);

Python

  1. # 获取图像响应函数 (CRF)
  2. calibrateDebevec = cv2.createCalibrateDebevec()
  3. responseDebevec = calibrateDebevec.process(images, times)

下图显示了使用红绿蓝通道的图像提取的 CRF。

Camera Response Function

Camera Response Function

第 4 步:合并图像

一旦 CRF 评估结束,我们可以使用 MergeDebevec 将曝光图像合并成一个 HDR 图像。 C++ 和 Python 代码如下所示。

C++

  1. // 将图像合并为 HDR 线性图像
  2. Mat hdrDebevec;
  3. Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
  4. mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
  5. // 保存图像
  6. imwrite("hdrDebevec.hdr", hdrDebevec);

Python

  1. # 将图像合并为 HDR线性图像
  2. mergeDebevec = cv2.createMergeDebevec()
  3. hdrDebevec = mergeDebevec.process(images, times, responseDebevec)
  4. # 保存图像
  5. cv2.imwrite("hdrDebevec.hdr", hdrDebevec)

上面保存的 HDR 图像可以在 Photoshop 中加载并进行色调映射。示例图像如下所示。

HDR Photoshop 色调映射

HDR Photoshop 色调映射

第 5 步:色调映射

现在我们已经将我们的曝光图像合并到一个 HDR 图像中。 你能猜出这个图像的最小和最大像素值吗? 对于黑色条件,最小值显然为 0。 理论最大值是什么? 无限大! 在实践中,不同情况下的最大值是不同的。 如果场景包含非常明亮的光源,那么最大值就会非常大。

尽管我们已经使用多个图像恢复了相对亮度信息,但是我们现在又面临了新的挑战:将这些信息保存为 24 位图像用于显示。

将高动态范围(HDR)图像转换为 8 位单通道图像的过程称为色调映射。这个过程的同时还需要保留尽可能多的细节。

有几种色调映射算法。 OpenCV 实现了其中的四个。 要记住的是没有一个绝对正确的方法来做色调映射。 通常,我们希望在色调映射图像中看到比任何一个曝光图像更多的细节。 有时色调映射的目标是产生逼真的图像,而且往往是产生超现实图像的目标。 在 OpenCV 中实现的算法倾向于产生现实的并不那么生动的结果。

我们来看看各种选项。 以下列出了不同色调映射算法的一些常见参数。

  1. 伽马 gamma:该参数通过应用伽马校正来压缩动态范围。 当伽马等于 1 时,不应用修正。 小于 1 的伽玛会使图像变暗,而大于 1 的伽马会使图像变亮。
  2. 饱和度 saturation:该参数用于增加或减少饱和度。 饱和度高时,色彩更丰富,更浓。 饱和度值接近零,使颜色逐渐消失为灰度。
  3. 对比度 contrast:控制输出图像的对比度(即 log(maxPixelValue/minPixelValue))。

让我们来探索 OpenCV 中可用的四种色调映射算法。

Drago 色调映射

Drago 色调映射的参数如下所示:

  1. createTonemapDrago
  2. (
  3. float gamma = 1.0f,
  4. float saturation = 1.0f,
  5. float bias = 0.85f
  6. )

这里, bias[0, 1] 范围内偏差函数的值。 从 0.7 到 0.9 的值通常效果较好。 默认值是 0.85。 有关更多技术细节,请参阅这篇 论文

C++ 和 Python 代码如下所示。 参数是通过反复试验获得的。 最后的结果乘以 3 只是因为它给出了最令人满意的结果。

C++

  1. // 使用 Drago 色调映射算法获得 24 位彩色图像
  2. Mat ldrDrago;
  3. Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.0, 0.7);
  4. tonemapDrago->process(hdrDebevec, ldrDrago);
  5. ldrDrago = 3 * ldrDrago;
  6. imwrite("ldr-Drago.jpg", ldrDrago * 255);

Python

  1. # 使用 Drago色调映射算法获得 24位彩色图像
  2. tonemapDrago = cv2.createTonemapDrago(1.0, 0.7)
  3. ldrDrago = tonemapDrago.process(hdrDebevec)
  4. ldrDrago = 3 * ldrDrago
  5. cv2.imwrite("ldr-Drago.jpg", ldrDrago * 255)

结果如下:

使用Drago算法的HDR色调映射

使用 Drago 算法的 HDR 色调映射

Durand 色调映射

Durand 色调映射的参数如下所示:

  1. createTonemapDurand
  2. (
  3. float gamma = 1.0f,
  4. float contrast = 4.0f,
  5. float saturation = 1.0f,
  6. float sigma_space = 2.0f,
  7. float sigma_color = 2.0f
  8. );

该算法基于将图像分解为基础层和细节层。 使用称为双边滤波器的边缘保留滤波器来获得基本层。 sigma_spacesigma_color 是双边滤波器的参数,分别控制空间域和彩色域中的平滑量。

有关更多详细信息,请查看这篇 论文

C++

  1. // 使用 Durand 色调映射算法获得 24 位彩色图像
  2. Mat ldrDurand;
  3. Ptr<TonemapDurand> tonemapDurand = createTonemapDurand(1.5,4,1.0,1,1);
  4. tonemapDurand->process(hdrDebevec, ldrDurand);
  5. ldrDurand = 3 * ldrDurand;
  6. imwrite("ldr-Durand.jpg", ldrDurand * 255);

Python

  1. # 使用 Durand色调映射算法获得 24位彩色图像
  2. tonemapDurand = cv2.createTonemapDurand(1.5,4,1.0,1,1)
  3. ldrDurand = tonemapDurand.process(hdrDebevec)
  4. ldrDurand = 3 * ldrDurand
  5. cv2.imwrite("ldr-Durand.jpg", ldrDurand * 255)

结果如下:

使用Durand算法的HDR色调映射

使用 Durand 算法的 HDR 色调映射

Reinhard 色调映射

  1. createTonemapReinhard
  2. (
  3. float gamma = 1.0f,
  4. float intensity = 0.0f,
  5. float light_adapt = 1.0f,
  6. float color_adapt = 0.0f
  7. )

intensity 参数应在 [-8, 8] 范围内。 更高的亮度值会产生更明亮的结果。 light_adapt 控制灯光,范围为 [0, 1]。 值 1 表示仅基于像素值的自适应,而值 0 表示全局自适应。 中间值可以用于两者的加权组合。 参数 color_adapt 控制色彩,范围为 [0, 1]。 如果值被设置为 1,则通道被独立处理,如果该值被设置为 0,则每个通道的适应级别相同。中间值可以用于两者的加权组合。

有关更多详细信息,请查看这篇 论文

C++

  1. // 使用 Reinhard 色调映射算法获得 24 位彩色图像
  2. Mat ldrReinhard;
  3. Ptr<TonemapReinhard> tonemapReinhard = createTonemapReinhard(1.5, 0,0,0);
  4. tonemapReinhard->process(hdrDebevec, ldrReinhard);
  5. imwrite("ldr-Reinhard.jpg", ldrReinhard * 255);

Python

  1. # 使用 Reinhard色调映射算法获得 24位彩色图像
  2. tonemapReinhard = cv2.createTonemapReinhard(1.5, 0,0,0)
  3. ldrReinhard = tonemapReinhard.process(hdrDebevec)
  4. cv2.imwrite("ldr-Reinhard.jpg", ldrReinhard * 255)

结果如下:

使用Reinhard算法的HDR色调映射

使用 Reinhard 算法的 HDR 色调映射

Mantiuk 色调映射

  1. createTonemapMantiuk
  2. (
  3. float gamma = 1.0f,
  4. float scale = 0.7f,
  5. float saturation = 1.0f
  6. )

参数 scale 是对比度比例因子。 从 0.7 到 0.9 的值通常效果较好

有关更多详细信息,请查看这篇 论文

C++

  1. // 使用 Mantiuk 色调映射算法获得 24 位彩色图像
  2. Mat ldrMantiuk;
  3. Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2,0.85, 1.2);
  4. tonemapMantiuk->process(hdrDebevec, ldrMantiuk);
  5. ldrMantiuk = 3 * ldrMantiuk;
  6. imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255);

Python

  1. # 使用 Mantiuk色调映射算法获得 24位彩色图像
  2. tonemapMantiuk = cv2.createTonemapMantiuk(2.2,0.85, 1.2)
  3. ldrMantiuk = tonemapMantiuk.process(hdrDebevec)
  4. ldrMantiuk = 3 * ldrMantiuk
  5. cv2.imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255)

结果如下:

使用Mantiuk算法的HDR色调映射

使用 Mantiuk 算法的 HDR 色调映射

订阅然后下载代码

如果你喜欢这篇文章,并希望下载本文中使用的代码(C++ 和 Python)和示例图片,请 订阅 我们的电子杂志。 您还将获得免费的 计算机视觉资源 指南。 在我们的电子杂志中,我们分享了用 C++ 还有 Python 编写的 OpenCV 教程和例子,以及计算机视觉和机器学习的算法和新闻。

点此订阅

图片致谢

本文中使用的四个曝光图像获得 CC BY-SA 3.0 许可,并从 维基百科的 HDR 页面 下载。 图像由 Kevin McCoy 拍摄。


作者简介:

我是一位热爱计算机视觉和机器学习的企业家,拥有十多年的实践经验(还有博士学位)。

2007 年,在完成博士学位之后,我和我的顾问 David Kriegman 博士还有 Kevin Barnes 共同创办了 TAAZ 公司。 我们的计算机视觉和机器学习算法的可扩展性和鲁棒性已经经过了试用了我们产品的超过 1 亿的用户的严格测试。


via: http://www.learnopencv.com/high-dynamic-range-hdr-imaging-using-opencv-cpp-python/

作者:SATYA MALLICK 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux 中国 荣誉推出

编译自: http://www.learnopencv.com/high-dynamic-range-hdr-imaging-using-opencv-cpp-python/ 作者: Satya Mallick
原创: LCTT https://linux.cn/article-9754-1.html 译者: Liang Chen

https://ift.tt/2tgLpEY