• What skills are needed for machine learning jobs


    What skills are needed for machine learning jobs?机器学习工作必须技能

    原文:

    http://www.quora.com/Machine-Learning/What-skills-are-needed-for-machine-learning-jobs/answer/Joseph-Misiti

     

    Machine Learning: What skills are needed for machine learning jobs?

    I am a learner sitting at home and learning linear algebra. Very interested in working in Machine Learning someday, but not sure:
    a) What technical skills are needed for an interview/job.
    b) Any relevant mandatory work experience.
    I have taken an initiative to at least start rather than just think about doing it so any suggestion/guidance would be very helpful and appreciated.

    Joseph MisitiI ♥ machine-learning

    In my opinion, these are some of the necessary skills:
    1. Python/C++/R/Java - you will probably want to learn all of these languages at some point if you want a job in machine-learning. Python's Numpy and Scipy libraries [2] are awesome because they have similar functionality to MATLAB, but can be easily integrated into a web service and also used in Hadoop (see below). C++ will be needed to speed code up. R [3] is great for statistics and plots, and Hadoop [4] is written in Java, so you may need to implement mappers and reducers in Java (although you could use a scripting language via Hadoop streaming [6])

    2. Probability and Statistics: A good portion of learning algorithms are based on this theory. Naive Bayes [6], Gaussian Mixture Models [7], Hidden Markov Models [8], to name a few. You need to have a firm understanding of Probability and Stats to understand these models. Go nuts and study measure theory [9]. Use statistics as an model evaluation metric: confusion matrices, receiver-operator curves, p-values, etc.

    3. Applied Math + Algorithms: For discriminate models like SVMs [10], you need to have a firm understanding of algorithm theory. Even though you will probably never need to implement an SVM from scratch, it helps to understand how the algorithm works. You will need to understand subjects like convex optimization [11], gradient decent [12], quadratic programming [13], lagrange [14], partial differential equations [15], etc. Get used to looking at summations [16].

    4. Distributed Computing: Most machine learning jobs require working with large data sets these days (see Data Science) [17]. You cannot process this data on a single machine, you will have to distribute it across an entire cluster. Projects like Apache Hadoop [4] and cloud services like Amazon's EC2 [18] makes this very easy and cost-effective. Although Hadoop abstracts away a lot of the hard-core, distributed computing problems, you still need to have a firm understanding of map-reduce [22], distribute-file systems [19], etc. You will most likely want to check out Apache Mahout [20] and Apache Whirr [21].

    5. Expertise in Unix Tools: Unless you are very fortunate, you are going to need to modify the format of your data sets so they can be loaded into R,Hadoop,HBase [23],etc. You can use a scripting language like python (using re) to do this but the best approach is probably just master all of the awesome unix tools that were designed for this: cat [24], grep [25], find [26], awk [27], sed [28], sort [29], cut [30], tr [31], and many more. Since all of the processing will most likely be on linux-based machine (Hadoop doesnt run on Window I believe), you will have access to these tools. You should learn to love them and use them as much as possible. They certainly have made my life a lot easier. A great example can be found here [1].

    6. Become familiar with the Hadoop sub-projects: HBase, Zookeeper [32], Hive [33], Mahout, etc. These projects can help you store/access your data, and they scale.

    7. Learn about advanced signal processing techniques: feature extraction is one of the most important parts of machine-learning. If your features suck, no matter which algorithm you choose, your going to see horrible performance. Depending on the type of problem you are trying to solve, you may be able to utilize really cool advance signal processing algorithms like: wavelets [42], shearlets [43], curvelets [44], contourlets [45], bandlets [46]. Learn about time-frequency analysis [47], and try to apply it to your problems. If you have not read about Fourier Analysis[48] and Convolution[49], you will need to learn about this stuff too. The ladder is signal processing 101 stuff though.

    Finally, practice and read as much as you can. In your free time, read papers like Google Map-Reduce [34], Google File System [35], Google Big Table [36], The Unreasonable Effectiveness of Data [37],etc There are great free machine learning books online and you should read those also. [38][39][40]. Here is an awesome course I found and re-posted on github [41]. Instead of using open source packages, code up your own, and compare the results. If you can code an SVM from scratch, you will understand the concept of support vectors, gamma, cost, hyperplanes, etc. It's easy to just load some data up and start training, the hard part is making sense of it all.

    Good luck.

    [1] http://radar.oreilly.com/2011/04...

    [2] http://numpy.scipy.org/

    [3] http://www.r-project.org/

    [4] http://hadoop.apache.org/

    [5] http://hadoop.apache.org/common/...

    [6] http://en.wikipedia.org/wiki/Nai...

    [7] http://en.wikipedia.org/wiki/Mix...

    [8] http://en.wikipedia.org/wiki/Hid...

    [9] http://en.wikipedia.org/wiki/Mea...

    [10] http://en.wikipedia.org/wiki/Sup...

    [11] http://en.wikipedia.org/wiki/Con...

    [12] http://en.wikipedia.org/wiki/Gra...

    [13] http://en.wikipedia.org/wiki/Qua...

    [14] http://en.wikipedia.org/wiki/Lag...

    [15] http://en.wikipedia.org/wiki/Par...

    [16] http://en.wikipedia.org/wiki/Sum...

    [17] http://radar.oreilly.com/2010/06...

    [18] http://aws.amazon.com/ec2/

    [19] http://en.wikipedia.org/wiki/Goo...

    [20] http://mahout.apache.org/

    [21] http://incubator.apache.org/whirr/

    [22] http://en.wikipedia.org/wiki/Map...

    [23] http://hbase.apache.org/

    [24] http://en.wikipedia.org/wiki/Cat...

    [25] http://en.wikipedia.org/wiki/Grep

    [26] http://en.wikipedia.org/wiki/Find

    [27] http://en.wikipedia.org/wiki/AWK

    [28] http://en.wikipedia.org/wiki/Sed

    [29] http://en.wikipedia.org/wiki/Sor...

    [30] http://en.wikipedia.org/wiki/Cut...

    [31] http://en.wikipedia.org/wiki/Tr_...

    [32] http://zookeeper.apache.org/

    [33] http://hive.apache.org/

    [34] http://static.googleusercontent....

    [35]http://static.googleusercontent....

    [36]http://static.googleusercontent....

    [37]http://static.googleusercontent....

    [38] http://www.ics.uci.edu/~welling/...

    [39] http://www.stanford.edu/~hastie/...

    [40] http://infolab.stanford.edu/~ull...

    [41] https://github.com/josephmisiti/...

    [42] http://en.wikipedia.org/wiki/Wav...

    [43] http://www.shearlet.uni-osnabrue...

    [44] http://math.mit.edu/icg/papers/F...

    [45] http://www.ifp.illinois.edu/~min...

    [46] http://www.cmap.polytechnique.fr...

    [47 ]http://en.wikipedia.org/wiki/Tim...

    [48] http://en.wikipedia.org/wiki/Fou...

    [49 ]http://en.wikipedia.org/wiki/Con...

    译文:

    译文

    Q:机器学习工作需要哪些技能

    我是一个在家自学机器学习的初学者,现在正在学习线性代数。

    我非常希望有朝一日能够从事于机器学习领域,但是我对以下几点不太确定:

    a)面试该工作或者胜任该工作需要哪些专业技能。

    b)是否要求相关领域的工作经验的硬性要求。

    我并非只是空想者,我已经迈出了主动开始的第一部,所以任何建议或者知道都会对我很有帮助,我也由衷地感激各位。

    A:

    在我看来,有一下必需技能:

    1、Python/C++/Java 如果你想要得到机器学习领域中的一份工作,你很可能会想要去学习这几个编程语言。Python的科学计算包(Numpy)与稀疏矩阵运算包(Scipy) [2]与MATLAB有相似的功能,因此显得很强大、很棒,但是与MATLAB不同的是,Python可以很容易集成到网络服务和应用在Hadoop。为了加速编码,C++佳能也是需要的。R [3]非常适用于统计与绘图。Hadoop [4]是用Java语言编写的,所以你可能需要用Java实现mappers和reducers(虽然你可以通过Hadoop streaming(Hadoop提供的一个编程工具)来使用一种脚本语言 [6])

    2、概率统计:机器学习很大的一部分是以这些理论为基础的。例如朴素贝叶斯,高斯混合模型 [7]、隐马尔可夫模型 [8]等等。你需要对概率学和统计学有一个透彻扎实的理解才能够明白这些模型。努力吧,少年,去学习测度论吧 [9]。把统计学作为一种模型评估标准,例如混淆矩阵、受试者工作曲线、p值等等都可作为评估标准。

    3、应用数学与算法:对一些识别模型,譬如支持向量机模型 [10],你需要对此模型算法理论有一个透彻扎实的理解。即使你很可能从来不需要从零做起去实现一个支持向量机,但是算法理论有助于你了解这个算法是如何运作的。你将需要去熟悉像凸优化 [11]、梯度下降 [12]、二次规划编程 [13]、拉格朗日 [14]、偏微方程 [15]等等概念。养成看总结 [16]的习惯。

    4、分布式计算:目前,大多数机器学习工作都需要与大数据集打交道(参考Data Science [17])。你不可能在一单机上处理这大数据集,你必须把这大数据集分布在整个计算机簇中。像Apache Hadoop项目与亚马逊的EC2 [18]云服务都使处理大数据集变得非常简单和低成本。虽然Hadoop把核心分布式计算问题的细节隐藏了很多,但是你还是需要对MapReduce [22]、分布式档案系统 [19]等等有一个很好的理解。你将很可能想去查询一下Apache Mahout [20]、Apache Whirr [21]。

    5、Unix工具的专业知识:除非你真的很幸运不需修改数据格式,不然你将需要修改你的数据集格式,使它能够在R、Hadoop、HBase [23]等平台上能够加载。虽然你可以用脚本语言,譬如python来完成这个,但是最好的方法可能就是掌握这些非常棒的、专门为处理数据而设计的unix工具,例如cat [24]、grep [25]、find [26]、awk [27]、sed [28]、sort [29]、cut [30]、tr [31]诸如此类非常多的。由于这些处理很可能都是在基于linux的机器上运行(我很肯定地相信Hadoop不能在window上运行),因此你将会接触这些工具。你应该学会去喜欢它们,尽可能地区使用它们。它们的确使我的生活变得更加方便、简单。这就是一个很好说明这些工作强大的例子。

    6、熟悉Hadoop的衍生项目:Hbase、Zookeeper [32]、Hive [33]、Ma货身体等等。这些项目能够帮你存放与使用数据,并且它们支持大规模数据处理。

    7、学习高级信号处理技术:特征提取是机器学习最重要组成部分之一。如果你的特征提取很糟糕,那么无论你选择哪一个算法,你都将会看到很差的性能。根据你着手尝试解决问题的类型,你也许能够利用非常酷的高级信号处理算法,譬如wavelets [42]、shearlets [43]、curvelets [44]、contourlets [45]、bandlets [46]。学会时频分析 [47],并且尝试将它应用到你的问题解决当中。即使你不读Fourier Analysi [48]和Convolution [49],你都应该读一下上述介绍的理论。而二进制码信号处理技术就是解决问题的途径。

    最后,尽管多练多看。在你空闲的时候,读一些Google Map-reduce [34]、Google File System [35]、Google Big Table [36]、The Unreasonable Effectiveness of Data [37]等等之类的文章。网络上有大量免费的机器学习电子书籍,你也应该读读这些书籍。 [38] [39] [40] 我发现了一个很棒的课程,并且把它转载到github [41]上。不要用开源包,你自己编代码做一个源码包,然后与开源包比较结果。如果你能够从零开始实现支持向量机,你将会理解support vector、gamma、cost、hyperplanes等等。加载数据、开始训练是很简单的事情,困难的是使这些过程都变得有意义

    译者: 林羽飞扬
    出处:http://www.cnblogs.com/zhengyuhong/
    原文版权归原作者,如译文有侵权行为,请联系译者,欢迎转载,但未经作者同意必须保留原作者与译者信息,且在文章页面明显位置给出原文连接

    一、参数标记

    /*
     * once: 确保回调列表仅只fire一次
     * unique: 在执行add操作中,确保回调列表中不存在重复的回调
     * stopOnFalse: 当执行回调返回值为false,则终止回调队列的执行
     * momery: 记录上一次fire时的参数,并在add中传递给fire和执行fire,执行时firingIndex为上一次fire时的firingLength
     */

    二、源码解读分析

    复制代码
    var optionsCache = {},
        // Used for splitting on whitespace
        core_rnotwhite = /S+/g;
    
    // Convert String-formatted options into Object-formatted ones and store in cache
    function createOptions( options ) {
        // 多个变量指向同一对象(或数组)引用时,其中一个变量修改了被引用对象的内部结构,其他引用变量也会表现出来
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true; // optionsCache[ options ][ flag ] = true;
        });
        return object;
    }
    
    jQuery.Callbacks = function( options ) {
    
        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        options = typeof options === "string" ?
            // 缓存所有的参数标志,当再次传递已传递过的参数标志,则使用缓存值optionsCache[ options ]        
            ( optionsCache[ options ] || createOptions( options ) ) :
    
            // 说明也可以这样$.Callbacks({once:true, memory:true})使用
            jQuery.extend( {}, options );
    
        var // Flag to know if list is currently firing
            firing,
            // Last fire value (for non-forgettable lists)
            memory,
            // Flag to know if list was already fired
            fired,
            // End of the loop when firing
            firingLength,
            // Index of currently firing callback (modified by remove if needed)
            firingIndex,
            // First callback to fire (used internally by add and fireWith)
            firingStart,
            // Actual callback list
            list = [],
            // Stack of fire calls for repeatable lists
            stack = !options.once && [],
    
            // Fire callbacks
            // data为fireWith内部整理的args数组
            fire = function( data ) {
                memory = options.memory && data;
                fired = true;
    
                // 处理在add中,options.memory = true;的情况
                firingIndex = firingStart || 0;
                firingStart = 0;
    
                firingLength = list.length;
                firing = true;
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    // 正在执行的回调返回值为false 且 options.stopOnFalse为true,则终止回调队列的执行
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        memory = false; // To prevent further calls using add
                        break;
                    }
                }
                firing = false;
                if ( list ) {
                    // 处理正在执行的回调中执行fireWith的操作;
                    if ( stack ) {
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    }
                    // 上一分支状态为回调执行过,且可以执行多次
                    // 此时 options.once = true; 为了保证回调列表只能fire一次,需将list设置为[];
                    else if ( memory ) {
                        list = [];
                    }
                    else {
                        self.disable();
                    }
                }
            },
            // Actual Callbacks object
            self = {
                // Add a callback or a collection of callbacks to the list
                add: function() {
                    if ( list ) {
                        // First, we save the current length
                        var start = list.length;
                        (function add( args ) {
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    // 回调不唯一 或 唯一且不存在,则push
                                    if ( !options.unique || !self.has( arg ) ) {
                                        list.push( arg );
                                    }
                                }
                                // 递归检查
                                else if ( arg && arg.length && type !== "string" ) {
                                    // Inspect recursively
                                    add( arg );
                                }
                            });
                        })( arguments );
                        // Do we need to add the callbacks to the
                        // current firing batch?
                        // 正在执行的回调执行了add操作,则更新firingLength
                        if ( firing ) {
                            firingLength = list.length;
    
                        // With memory, if we're not firing then
                        // we should call right away
                        // 如果options.memory为true,则再次执行fire,且参数相同,fire中的firingIndex为此时的firingStart
                        }
                        else if ( memory ) {
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                // Remove a callback from the list
                remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;                        
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                // 查找到所对应的索引,则移除索引项
                                list.splice( index, 1 );
    
                                // Handle firing indexes
                                // 正在执行的回调执行了remvoe操作,则更新firingLength和firingIndex的值
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },
                // Check if a given callback is in the list.
                // If no argument is given, return whether or not list has callbacks attached.
                has: function( fn ) {
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
                // Remove all callbacks from the list
                empty: function() {
                    list = [];
                    return this;
                },
                // Have the list do nothing anymore
                // 禁用add,remove,fire主要方法的工作
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state
                // 类似disable方法,!!memory例外
                lock: function() {
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },
                // Is it locked?
                locked: function() {
                    return !stack;
                },
                // Call all callbacks with the given context and arguments
                fireWith: function( context, args ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    
                    // 回调未执行 或 已执行且可以执行多次
                    if ( list && ( !fired || stack ) ) {
    
                        // 正在执行的回调函数执行了fireWith操作( 暗指回调列表已执行过,且可以执行多次,stack = []; )
                        // 该函数需要条件执行,或有移除该函数的操作,否则陷入死循环,详见例2
                        if ( firing ) {
                            stack.push( args );
                        }
                        // 正在执行的回调函数没有执行fireWith操作
                        else {
                            fire( args );
                        }
                    }
                    return this;
                },
                // Call all the callbacks with the given arguments
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                // To know if the callbacks have already been called at least once
                fired: function() {
                    return !!fired;
                }
            };
        return self;
    };
    复制代码

    三、示例

    例1:

    复制代码
    $(function(){
        // 定义三个将要增加到回调列表的回调函数fn1,fn2,fn3        
        function fn1(arg){
            console.log( 'fn1 says:' + arg );
            // 在fn1中执行Callbacks.add操作,此时Callbacks函数内部的firingLength将会得到更新
            $callbacks.add(fn2);
        }
        function fn2(arg){
            console.log( 'fn2 says:' + arg );
        }
        function fn3(arg){
            console.log( 'fn3 says:' + arg );
        }
        
        // Callbacks传递了memory
        // 也可以这样使用$.Callbacks({ memory: true });
        var $callbacks = $.Callbacks('memory');
        
        // 将fn1增加到回调列表中,因为在fn1中有执行了add(fn2)操作,因此回调列表中的回调为fn1,fn2
        $callbacks.add(fn1);
        
        // output: fn1 says:foo
        // output: fn2 says:foo
        $callbacks.fire('foo');
        
        // 将之前fire的参数传递给最近增加的回调fn3,并执行fn3
        // output: fn3 says:foo
        $callbacks.add(fn3);
        
        // 再执行一次fire,注意此时回调列表中的回调一次是fn1,fn2,fn3,fn2
        // output: fn1 says:baz
        // output: fn2 says:baz
        // output: fn3 says:baz
        // output: fn2 says:baz
        // 如果期望回调列表中只有fn1,fn2,fn3,只需在Callbacks函数中传入unique
        $callbacks.fire('baz');
    });
    复制代码

    例2

    复制代码
    $(function(){    
        function fn1(arg){
            console.log( 'fn1 says:' + arg );
        }
        function fn2(arg){
            console.log( 'fn2 says:' + arg );
            $callbacks.fireWith(window, ['yjh']);
            
            // 一定要执行这一步,否则将会陷入死循环
            $callbacks.remove(fn2);
        }
        
        var $callbacks = $.Callbacks();
        $callbacks.add(fn1);
        
        // output: fn1 says:foo
        $callbacks.fire('foo');    
    
        $callbacks.add(fn2);    
        // output: fn1 says:baz
        // output: fn2 says:baz
        // output: fn1 says:yjh
        $callbacks.fire('baz');
    });
    复制代码

    PS:

    此前写过一篇关于jQuery.Callbacks源码分析的随笔,理解不透彻,今天又重新翻阅了一下,记录一下自己的源码阅读,相比之前,感觉好多了。

    阅读前,可以先看API,弄清楚四个参数标志,'once', 'memory', 'unique', 'stopOnFalse', 简单的执行add, fire操作,然后再看源码;

    阅读顺序:

    1、先阅读var声明的变量,fire函数的前半部分,self对象中的add, remove函数,有些难以理解暂时往下看;

    2、然后阅读self对象中的fire,fireWith,最后再来阅读fire函数,弄清楚后再看其他self对象中的方法。

    转载请注明出处:博客园华子yjh

    Erlang的调度原理(译文)

    原文 http://jlouisramblings.blogspot.com/2013/01/how-erlang-does-scheduling.html

    免爬墙链接 http://www.dikutal.dk/blog/jlouis/how-erlang-does-scheduling

    Jesper Louis Andersen,2013年1月12日

    我用这篇文章解释一下Erlang和其他语言运行时相比不同之处。我还要解释为什么Erlang往往会牺牲吞吐换取更低的延迟。

    太长了,我懒得读(译者注:原文TL;DR表示too long, didn’t read。原作者可能是在参与和别人的讨论中写的这篇文章,“懒得读”表现出一种“懒得跟你们争了”的有趣心态。校园网爬墙速度太慢,所以没有验证。)——Erlang和其他语言运行时不同之处在于关注的重点不同。本文描述了为什么进程很少的时候往往看上去似乎性能很差,但是进程很多的时候却表现得很好。

    老是有人会问Erlang调度的原理。本文只是对Erlang真正调度原理的简单概述,但是描述了Erlang对其进程的操作方式。注意我这里讨论的是Erlang R15。未来Erlang可能会发生很大的变化,但是不论是Erlang还是其他系统,事物总是会朝着更好的方向发展。

    从操作系统的角度来说,Erlang通常在机器上的每一个处理器核心上跑一个线程。每一个线程运行一个调度器。这种设定是为了确保机器上所有的核心都可以为Erlang系统卖力。通过+sbt参数可以将处理器核心和调度器绑定,也就是说调度器不会在众多核心之间跳来跳去。调度器绑定仅限于现代操作系统,因此OS X自然做不到。要实现调度器绑定,说明Erlang系统了解处理器的拓扑结构以及处理器相关的亲缘性,由于高速缓存以及迁移时间等原因,这些信息非常重要。设置+sbt参数通常都可以提升系统的速度。而且有的时候还能提升不少。

    +A参数定义了异步线程池中异步线程的数量。驱动程序可以通过异步线程池中的线程执行阻塞的操作,这样调度器可以在线程池中有些线程阻塞的情况下依然执行其他有用的工作。最值得注意的是,文件驱动程序通过线程池加速文件I/O,而网络I/O则没有使用线程池。

    以上内容是从操作系统内核的角度描述的,下面我们来理清Erlang进程(隶属于用户空间)的概念。通过调用spawn(fun worker/0)可以构建一个新的进程,Erlang系统会在用户空间分配进程控制块。一个进程通常需要大约600多个字节,而且32位系统和64位系统会有不同。可运行的进程放在调度器的运行队列中,之后获得时间片的时候就可以运行。

    在深入描述单个调度器之前,我先简要地描述一下迁移(migration)的工作原理。每过一段时间,调度器就会通过一个非常复杂的过程在调度器之间迁移一些进程。这种启发式迁移的目标就是为了平衡多个调度器的负载,使得所有的核心都能得到充分的利用。这个平衡算法还要考虑工作量是否足够大,是否大到需要启动一些新的调度器。如果没那么大的话,那么最好让那些调度器保持关闭状态,因为反正那些线程也没有工作可做。关闭调度器意味着处理器核心可以进入节能状态,甚至关闭核心。没错,Erlang会尽可能地节省电源消耗。如果调度器做完了工作,还会从其他调度器“窃取”工作(work-steal)。细节请参见[1]。

    重要:在R15中,调度器的启动和停止是有延迟(lagged)的。因为Erlang/OTP深知启动或停止一个调度器的开销非常大,所以不是真正需要的话是不会采取行动的。假设某一个调度器现在无事可做,那么系统不会立即将这个调度器设置为睡眠状态,而是会自旋等待一阵子,期待有任务会立即到达。如果有任务到达,那么调度器可以以低延迟立即开始处理任务。换句话说,不能使用top(1)这类工具或通过操作系统内核来测量系统执行的效率,而是必须使用Erlang系统的内部调用来测量。正因为这个原因,有不少人错误地认为R15不如R14高效。

    每一个调度器都运行两类作业:进程作业和port作业。这些作业运行的时候是带有优先级的,就像操作系统内核一样,因此也会面对和操作系统内核一样的担忧和启发式调度。进程可以标记高优先级和低优先级等优先级。进程作业执行一个进程一小段时间。port作业考虑的是port。如果你不知道port是什么,我简单解释一下port:port就是Erlang中一种和外部世界通信的机制。文件、网络套接字、和其他程序之间建立的管道,在Erlang中都是通过port实现的。开发者可以在Erlang系统中添加“port驱动程序”来支持新的port类型,不过这就要求编写C语言代码了。调度器还要对网络套接字进行轮询(polling),这样才能从网络中读取数据。

    进程和端口都有一个“reduction预算”,即2000个reduction。系统中的任何操作都要消耗reduction。这些操作包括循环中的函数调用、BIF(内建函数)的调用、进程中堆的垃圾回收[注1]、存取ETS和发送消息等(发送消息要考虑接收者的邮箱大小,邮箱越大发送的代价越高)。顺便提一下,会扣除reduction的地方遍及整个Erlang系统。比如Erlang的正则表达式库就做了修改,即使这个库是用C语言编写的,其中也添加了扣除reduction的相关代码。因此如果你有一个非常耗时的正则表达式,那么这个正则表达式的操作也会扣除reduction,所以在运行的过程中有可能会被抢占好几次。port也不例外!在一个port上执行I/O操作会消耗reduction,发送分布式消息也会消耗reduction,不胜枚举。开发者花了大量时间确保系统中产生的每一步进度都会消耗reduction[注2]。

    实际上,这就是为什么我说Erlang是真正能够实现抢占式多任务并且能真正做好软实时的少数语言之一的原因。Erlang更看重的是低延迟而不是单纯的吞吐量,这在程序设计语言运行时中是不多见的。

    准确地说,抢占(preemption)[2]指的是调度器能够强制剥夺任务的执行。所有基于协作(cooperation)的多任务都是做不到抢占的,例如Python的twisted库、Node.js和LWT(Ocaml)等。但是更有意思的是,Go(golang.org)和Haskell(GHC)也都不是完全抢占式的。Go只有在通信的时候会发生上下文切换,因此一个密集的循环就会霸占整个处理器核心。GHC会在内存分配的时候发生切换(不得不承认内存分配是Haskell程序中一个非常频繁的操作)。这些系统的问题在于,将处理器核心霸占一段时间的后果就是影响系统的响应延迟——想象一下这两种语言执行数组操作的时候的情景。

    这就引出了软实时(soft-realtime)[3]的概念,软实时指的是如果无法满足时间截止线需求的时候会导致系统服务水准降级(而不是整个失败)。假设在运行队列中有500有100个进程。第一个进程正在做一个耗时50毫秒的数组操作。在Go或Haskell/GHC[注3]中,这意味着任务2-100都需要至少50ms。而在Erlang中则不同,任务1有2000个reduction的预算,相当于大约1ms的时间。然后用完reduction预算后,任务1会被放回运行队列,这样任务2-任务100就有机会运行。这自然意味着所有的任务都有公平的时间份额。

    Erlang是为保证低延迟软实时的特性而精心打造的。2000的reduction预算很低,会导致很多小的上下文切换。耗时长的BIF在计算过程中被抢占的代价非常高昂。但是这样可以保证Erlang在系统负载更高的情况下能够优雅地降级。对于像Ericsson这样在乎低延迟的公司来说,这也意味着别无选择了。你不可能神奇地找到另外一种为吞吐量打造的语言同时也获得低延迟的好处,你必须为之付出努力。如果低延迟对你来说很重要,那么平心而论,不选Erlang反而显得很奇怪了。

    [1] "Characterizing the Scalability of Erlang VM on Many-core Processors"http://kth.diva-portal.org/smash/record.jsf?searchId=2&pid=diva2:392243
    [2] http://en.wikipedia.org/wiki/Preemption_(computing)
    [3] http://en.wikipedia.org/wiki/Real-time_computing

    [注1] 进程堆是每个进程私有的,因此一个进程不会对其他进程的GC时间造成太大影响。

    [注2] 这段话也点明了为什么要小心耗时长的NIF的原因。NIF默认不会被抢占,而且也不会贡献reduction计数器。因此耗时长的NIF会引入系统延迟。

    [注3] 这里考虑单核心的情况,多核心能在一定程度上“掩盖”单核心的这个问题,但是问题依然存在。

    (2013年1月14日对本文稍有更新)

  • 相关阅读:
    个人博客
    个人博客
    5.4
    4.30
    4.29
    4.28
    4.27
    4.26
    4.25
    4.24
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3381570.html
Copyright © 2020-2023  润新知