zoukankan      html  css  js  c++  java
  • Life is short

        相信不少码农曾看过类似“life is short, use Python”等之类略带调侃意味的小段子(譬如我),而其也并非不无道理。每门编程语言都是合理的存在,都有它们的优点,及缺陷。

        码农们也大多学过用过不止一门语言,譬如我。

        像我这样曾胡摸过 N 门语言,但只会勉强熟练使用其中两三门或三四门的码农(C++、C#、Delphi、Golang),却还是想浅浅地比较一下它们,算是一点皮毛心得吧。

        从这个偶然看到的例子开始。

    uses
      SyncObjs, System.Threading, System.Diagnostics;
    
    {function local to the unit}
    function IsPrime (N: Integer): Boolean;
    var
      Test: Integer;
    begin
      IsPrime := True;
      for Test := 2 to N - 1 do
        if (N mod Test) = 0 then
        begin
          IsPrime := False;
          break; {jump out of the for loop}
        end;
    end;
    
    const
      Max = 100000; // 100K
    
    procedure TFormParallelFor.btnPlainForLoopClick(Sender: TObject);
    var
      I, Tot: Integer;
      Ticks: Cardinal;
    begin
      // counts the prime numbers below a given value
      Tot := 0;
      Ticks := GetTickCount;
      for I := 1 to Max do
      begin
        if IsPrime (I) then
          Inc (Tot);
        //Application.ProcessMessages;
      end;
      Ticks := GetTickCount - Ticks;
      Memo1.Lines.Add (Format (
        'Plain for: %d - %d', [Ticks, Tot]));
    end;
    
    procedure TFormParallelFor.btnParallelForLoopClick(Sender: TObject);
    var
      Tot: Integer;
      Ticks: Cardinal;
    begin
      Tot := 0;
      Ticks := GetTickCount;
      TParallel.For(1, Max, procedure (I: Int64)
        begin
          if IsPrime (I) then
            InterlockedIncrement (Tot);
        end);
      Ticks := GetTickCount - Ticks;
      Memo1.Lines.Add (Format (
        'Parallel for: %d - %d', [Ticks, Tot]));
    end;
    

      Delphi XE7 新引入了一个并发辅助库(似乎由 Allen Bauer 操的刀),针对多核编程提供了语言层面上的支持。刚好在逛 Embarcadero 论坛的时候看到了这篇浅显的文章,得知 Delphi 是通过其自实现的可自动伸缩、非常智能的线程池的方式来实现对并发的支持,其中有一条定义引起了我的注意:MaxThreadsPerCPU = 25(当然我还不清楚为何每个核上挂的最大线程数量是 25)。我已懒得去下载去安装庞大的 XE7 去看其相关的 RTL 源码了,但既然我刚好又有个官方的小 Demo,索性跑跑看,如下。

        看上去确实,这个并行库是比较不错的。

        于是我有点心痒了,不知如果用 Golang 来做同样的实现,速度会如何呢?如下(注:因只想单纯测试 goroutine 和 channel,所以实现代码没有使用原子操作包 sync/atomic 提供的功能。而我并不熟 OpenMP,所以也没作这方面的对比)。

    // ParallelCalcDemo project main.go
    package main
    
    import (
    	"fmt"
    	"runtime"	
    	"time"
    )
    
    const cNumMax = 100000
    
    func IsPrime(num int) bool {
    	ret := true
    	for i := 2; i < num; i++ {
    		if num%i == 0 {
    			ret = false
    			break
    		}
    	}
    	return ret
    }
    
    func plainCalc() {
    	cnt := 0
    	t1 := time.Now()
    	for i := 1; i <= cNumMax; i++ {
    		if IsPrime(i) {
    			cnt++
    		}
    	}
    	t2 := time.Now()
    	fmt.Printf("    plainCalc - Counts: %d; Time: %dms
    ", cnt, t2.Sub(t1)/time.Millisecond)
    }
    
    func parallelHandle(numMin, numMax int, ch chan int) {
    	cnt := 0
    	for i := numMin; i <= numMax; i++ {
    		if IsPrime(i) {
    			cnt++
    		}
    	}
    	ch <- cnt
    }
    
    func parallelCalc(numGoroutines int) {
    	t1 := time.Now()
    	chans := make(chan int, numGoroutines)
    	seg := cNumMax / numGoroutines
    
    	if cNumMax%numGoroutines != 0 {
    		seg++
    	}
    	for i := 0; i < numGoroutines-1; i++ {
    		go parallelHandle(1+i*seg, (i+1)*seg, chans)
    	}
    	go parallelHandle(1+(numGoroutines-1)*seg, cNumMax, chans)
    
    	cnt := 0
    	for i := 0; i < numGoroutines; i++ {
    		cnt += <-chans
    	}
    	t2 := time.Now()
    
    	fmt.Printf(" parallelCalc - Counts: %d; Time: %dms
    ", cnt, t2.Sub(t1)/time.Millisecond)
    }
    
    func parallelHandle2(value int, ch chan int) {
    	if IsPrime(value) {
    		ch <- 1
    		return
    	}
    	ch <- 0
    }
    
    func parallelCalc2() {
    	t1 := time.Now()
    	chans := make(chan int, cNumMax)
    	for i := 1; i <= cNumMax; i++ {
    		go parallelHandle2(i, chans)
    	}
    	cnt := 0
    	for i := 1; i <= cNumMax; i++ {
    		cnt += <-chans
    	}
    	t2 := time.Now()
    	fmt.Printf("parallelCalc2 - Counts: %d; Time: %dms
    ", cnt, t2.Sub(t1)/time.Millisecond)
    }
    
    func main() {
    	numGoroutines := runtime.NumCPU()
    	runtime.GOMAXPROCS(numGoroutines)
    	plainCalc()
    	parallelCalc(numGoroutines)
    	parallelCalc2()
    }
    

      小跑 10 次,结果如下。

        可见效率也还是可以的,虽然比 Delphi 的并行版本要慢几个百分点。只是既然前文有提到 25 这个数字,我想为何不尝试下将 GOMAXPROCS 设为 runtime.NumCPU() * 25 呢?如是略作改动之前的代码,得到如下结果。

        于是竟然发现,(并行)效率似乎比修改前的要高一点,跟 Delphi 的并行版本基本差不多了。综合网上的一些信息,似乎 Golang 的调度器还是不够“NB”:)

        当然,譬如对于 parallelCalc2 版本的实现,可能只是因开了 100K 个 goroutine 后引来的丁点调度效率损失而已。

        但即便如此,即便只是通过上面简单的代码片段,我们还是可以看到,在 Golang 里实现并发实在是太方便了,goroutine 和 channel 实在是非常廉价却又非常简洁高效的并发利器。而这样的特性对于服务器端开发来说同样非常有用且重要,很大程度上简化了服务器端的多线程相关逻辑。在语言核心层面支持并行和分布式,并发模型简洁高效,简直是天生的后端开发牛刀。

        譬如如下的代码片段,正是其威力的小小体现。

    // SimpleTcpServer project main.go
    package main
    
    import (
    	"fmt"
    	"net"
    	"strings"
    )
    
    func main() {
    	fmt.Println("Starting the server...")
    
    	listener, err := net.Listen("tcp", "localhost:50000")
    	if err != nil {
    		fmt.Println("Error listening", err.Error())
    		return
    	}
    
    	for {
    		conn, err := listener.Accept()
    		if err != nil {
    			fmt.Println("Error accepting", err.Error())
    			return
    		}
    		go doStuff(conn)
    	}
    }
    
    func doStuff(conn net.Conn) {
    	for {
    		buf := make([]byte, 512)
    		_, err := conn.Read(buf)
    		if err != nil {
    			fmt.Println("Error reading", err.Error())
    			return
    		}
    		fmt.Printf("Received data: %v", strings.Trim(string(buf), " "))
    	}
    }
    

        所以,Golang 还是有点意思的,对吧?

        刚开始学习 Golang 时,其似乎有点“反人类”的语法曾让我比较不适应(估计不少码农或多或少也会如此感觉),但熟悉一段时间后居然也算很快就看顺眼了。其譬如 comma, ok form、类型推导(当然 C++11 有 auto,C# 也有这语法特性)、延迟执行等语法糖特性确实比较简洁,但同时却功能强大且实用,写惯了 try..catch / try...except...finally等类似代码、习惯了通过各种手段来释放清除资源的我们,在遇到 comma, ok、defer 等这样非常简洁的语法时,会不会有惊喜甚至拍案叫绝的感受呢(对于我,好像有过)?

        顺便提一句,C# 里的 try..except..finally 类似于 Delphi 的相关语法,但不同的是后者却只有 try..except 和 try..finally,一直都没有加入前者这样的增强特性,我有点想不通。

        我们习惯了输入/遇到类似 private、public、protected 等所谓关键字,甚至更甚,internal、protected internal 等,而 Golang 仅以首字符大小写来控制访问权限实现相同效果,不能不说比较新颖、简洁。有时候我居然会想,譬如 C++ / C# 引入了许多确实看起来实用的语法糖,但它们很少甚至极少用到,且绝大多数完全可以由用户(即码农)改进其设计的方式来实现,而既如此,为什么要引入那些呢,它们真的对所谓的软件工程有比较明显的益处?

        C++ 有那么多的语言语法特性,但扪心自问,我们实际开发中真正用到了多少?大多数时候我们都只是使用其很小的一部分子集而已。而其本身的语言复杂度,带来了大幅提升的学习及使用成本,带来了非常复杂冗长的编译过程,也带来了(不合格)程序员滥用某些特性的可能。即便它能生成非常高效(及紧凑的)的机器码,但这背后的成本实在太大了。

        再顺便提一句,说到 private、public 等,Delphi 的 published 特性确实对写 UI 相关的程序时非常好用且很实用,相信用过 Delphi 的码农会有体会(C# 码农同理)。

        用 C++ 做 UI?唉。

        getter / setter? property 多么好的包装了它们。

        我曾想在 C++ 里模拟 Delphi 的 initialization 和 finalization 特性,但却没能很好的如愿(当然,C++Builder 有这个特性)。但 Golang 提供了 init,这样的语言设计细节,还是带给我不少欢喜的(Golang 有 GC,所以 finalization 存在的意义不大)。

        然后是譬如代码格式化等算不上语言特性的特性,我个人还是感觉比较有意义且实用的,尤其是对于有所谓代码洁癖的码农来说:)

        同样,无需分号结束每行代码等特性,确是有点实际意义的,而这些点滴的语言特性累积,才能便于使用它的码农写出简洁、直观的代码。

        只是,虽然 Golang 的 goroutine / channel 很强大简洁,但还是需要不少实践及实例去领会理解,譬如下面这段看似简单的代码,是不是有些“抽象”:)

    // primesieve project main.go
    package main
    
    import (
    	"fmt"
    )
    
    // Send the sequence 2, 3, 4, ... to channel ch
    func generate(ch chan int) {
    	for i := 2; ; i++ {
    		ch <- i
    	}
    }
    
    // Copy the values from channel in to channel out, removing those divisible by prime
    func filter(in, out chan int, prime int) {
    	for {
    		i := <-in
    		if i%prime != 0 {
    			out <- i
    		}
    	}
    }
    
    func main() {
    	ch := make(chan int)
    	go generate(ch)
    	for {
    		prime := <-ch
    		fmt.Print(prime, " ")
    		ch1 := make(chan int)
    		go filter(ch, ch1, prime)
    		ch = ch1
    	}
    }
    

        但既然 goroutine / channel 的作用是那么大,用起来又这样简洁,把花在研究奇技淫巧上的时间拿来学习领会它们,为何不可?

        Life is short, try Golang。

        作为曾重度使用过 Delphi 的码农,很费解为何其一直在标榜所谓的数据库开发,即便两年前移动游戏开发已开始如火如荼,它却始终没能提供,或支持其爱好者提供可用的框架(想想下 Cocos2d 吧)。这次大潮它没赶上,估计也不会赶上了。顺势太重要,错过难再有。

        虽然它早已能做多平台的应用开发,但始终,它恐怕只将自身定位为所谓的应用 APP 开发工具而已,中庸普通且平凡。

        即便在某些方面,它确实很方便,甚至是利器。

  • 相关阅读:
    卓京---java基础2
    GuessFist
    猜拳 GuessFist
    GuessNum
    GuessNumber
    JetBrains全系列软件激活教程激活码以及JetBrains系列软件汉化包
    两个class 之间要空两行
    ImageField 字段的使用
    max_length 属性
    null,blank,default
  • 原文地址:https://www.cnblogs.com/ecofast/p/4079607.html
Copyright © 2011-2022 走看看