相信不少码农曾看过类似“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\n", 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\n", 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\n", 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 开发工具而已,中庸普通且平凡。

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

Life is short的更多相关文章

  1. 游戏引擎架构 (Jason Gregory 著)

    第一部分 基础 第1章 导论 (已看) 第2章 专业工具 (已看) 第3章 游戏软件工程基础 (已看) 第4章 游戏所需的三维数学 (已看) 第二部分 低阶引擎系统 第5章 游戏支持系统 (已看) 第 ...

  2. JAVA 各种数值类型最大值和最小值 Int, short, char, long, float,&nbs

    转载地址:http://blog.sina.com.cn/s/blog_5eab3d430101fdv6.html 代码片段: fmax = Float.MAX_VALUE; fmin = Float ...

  3. EntityFramework 7 smallint short 奇怪问题(已解决)

    在使用 EF7 进行条件查询的时候,遇到一个很奇怪的问题,不知道 EF 其他版本有没有这种情况,怎么说呢?一句话描述不清楚,具体请看下面内容. 问题场景 BloggingContext 配置代码: u ...

  4. plist文件里的"Bundle versions string, short" 跟 "Bundle version" 的区别及作用

    Bundle versions string, short:用于itunes上显示的版本号,即对外的版本,一般除了版本迭代外,不能随意更改. Bundle version:内部项目管理的版本号,是给程 ...

  5. c# unchecked关键字。byte 合并short

    参考MSDN 代码: public class BytesOperate { /// <summary> /// 计算校验和,SUM /// </summary> public ...

  6. C# 与数据库中字段类型 Int16(short), Int32(int), Int64(long)的取值范围、区别 。string长度

    一开始看到Int16, Int32, Int64这三种类型就觉得有点怪, 为什么要整个数字结尾的, 挺怪的. 昨天互相想到, ms这么干就是想让大家一眼就知道这个数据类型占多大空间吧. Int8, 等 ...

  7. 源码阅读笔记 - 3 std::string 与 Short String Optimization

    众所周知,大部分情况下,操作一个自动(栈)变量的速度是比操作一个堆上的值的速度快的.然而,栈数组的大小是在编译时确定的(不要说 C99 的VLA,那货的 sizeof 是运行时计算的),但是堆数组的大 ...

  8. APP One Link ,android and ios qrcode merge as One QRCode and one short link

    Adroid and ios qrcode merge as One QRCode and one short link is publish , the web site is www.appone ...

  9. char,short ,int ,long,long long,unsigned long long数据范围

    from:http://www.cnblogs.com/A123456A/archive/2013/01/25/2876634.html char,short ,int ,long,long long ...

随机推荐

  1. 常用数据存储集合与Map

    一.常见的数据存储实现 二.遍历 1.集合 新循环 迭代器    Iterator Iterator<?> it = C.iterator(); // 问.取.删 it.hasNext() ...

  2. canvas 拖拽实现

    Canvas 依赖分辨率 不支持事件处理器 弱的文本渲染能力 能够以 .png 或 .jpg 格式保存结果图像 最适合图像密集型的游戏,其中的许多对象会被频繁重绘 SVG 不依赖分辨率 支持事件处理器 ...

  3. SqlServer触发器判断对表操作类型(增、删、改)并将修改后的数据映射到新表

    该文章为原创,日后可能会根据实际开发经验和网友评论,进行相应地方修改,为获得最新博客动态,望在转发博客的时候注明出处. 触发器要实现的功能: (1)获取对表Table1数据操作操作类型(insert. ...

  4. 轻量级ORM-Fluentdata入门

    Fluent Data 入门 由 Primates 根据互联网资源整理FluentData 是微型 ORM(micro-ORM)家族的一名新成员,旨在比大型 ORM(full ORM)更加易用.Flu ...

  5. 如何使用Profiler跟踪阻塞信息

    Blocked Process ReportProfiler提供一个被阻塞进程报告的事件Errors and Warnings->Blocked Process Report 指示某个任务已被阻 ...

  6. CentOS 7 安装字体库 & 中文字体

    前言 报表中发现有中文乱码和中文字体不整齐(重叠)的情况,首先考虑的就是操作系统是否有中文字体,在CentOS 7中发现输入命令查看字体列表是提示命令无效:  如上图可以看出,不仅没有中文字体,连字体 ...

  7. hdfs shell 命令以及原理

    shell 操作 dfs 上传[hadoop@namenode ~]$ /data/hadoop/bin/hadoop fs -put /opt/MegaRAID/MegaCli/MegaCli64 ...

  8. Angular动态编译Html

    //取得scope,有时可以使用this var scope=angular.element(this).scope(); var el= $compile(html)(scope); //加入到Do ...

  9. weblogic

    停止(2) 更新 原路径 下一步 激活更新 启动(1)

  10. 向ES6看齐,用更好的JavaScript(一)

    众所周知,JavaScript作为弱类型语言,一直是精华与糟粕共存,许多"诡异"的地方我们不得不接受并使用.其实ES6(又称ECMAScript 2015)在2015年6月就已经正 ...