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\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的更多相关文章
- 游戏引擎架构 (Jason Gregory 著)
第一部分 基础 第1章 导论 (已看) 第2章 专业工具 (已看) 第3章 游戏软件工程基础 (已看) 第4章 游戏所需的三维数学 (已看) 第二部分 低阶引擎系统 第5章 游戏支持系统 (已看) 第 ...
- JAVA 各种数值类型最大值和最小值 Int, short, char, long, float,&nbs
转载地址:http://blog.sina.com.cn/s/blog_5eab3d430101fdv6.html 代码片段: fmax = Float.MAX_VALUE; fmin = Float ...
- EntityFramework 7 smallint short 奇怪问题(已解决)
在使用 EF7 进行条件查询的时候,遇到一个很奇怪的问题,不知道 EF 其他版本有没有这种情况,怎么说呢?一句话描述不清楚,具体请看下面内容. 问题场景 BloggingContext 配置代码: u ...
- plist文件里的"Bundle versions string, short" 跟 "Bundle version" 的区别及作用
Bundle versions string, short:用于itunes上显示的版本号,即对外的版本,一般除了版本迭代外,不能随意更改. Bundle version:内部项目管理的版本号,是给程 ...
- c# unchecked关键字。byte 合并short
参考MSDN 代码: public class BytesOperate { /// <summary> /// 计算校验和,SUM /// </summary> public ...
- C# 与数据库中字段类型 Int16(short), Int32(int), Int64(long)的取值范围、区别 。string长度
一开始看到Int16, Int32, Int64这三种类型就觉得有点怪, 为什么要整个数字结尾的, 挺怪的. 昨天互相想到, ms这么干就是想让大家一眼就知道这个数据类型占多大空间吧. Int8, 等 ...
- 源码阅读笔记 - 3 std::string 与 Short String Optimization
众所周知,大部分情况下,操作一个自动(栈)变量的速度是比操作一个堆上的值的速度快的.然而,栈数组的大小是在编译时确定的(不要说 C99 的VLA,那货的 sizeof 是运行时计算的),但是堆数组的大 ...
- 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 ...
- 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 ...
随机推荐
- jquery easyui tree的全选与反选
//全选反选 //参数:selected:传入this,表示当前点击的组件 //treeMenu:要操作的tree的id:如:id="userTree" function tree ...
- Instsrv.exe和Srvany.exe的使用方法
要把应用程序添加为服务,你需要两个小软件:Instsrv.exe和Srvany.exe.Instsrv.exe可以给系统安装和删除服务,Srvany.exe可以让程序以服务的方式运行.这两个软件都包含 ...
- 利用crontab系统每天定时备份MySQL数据库
利用系统crontab来定时执行备份文件,按日期对备份结果进行保存,达到备份的目的. 1.创建保存备份文件的路径/mysqldata 1 #mkdir /mysqldata 2.创建/usr/sb ...
- 简单的js菜单
<!DOCTYPE html> <html> <head> <title>hovertree</title><base target= ...
- MyBatis操作指南-与Spring集成(基于注解)
- C# GMap下提供一个高德地图
using System; using GMap.NET.Internals; using GMap.NET.Projections; namespace GMap.NET.MapProviders ...
- 相似度分析,循环读入文件(加入了HanLP,算法第四版的库)
相似度分析的,其中的分词可以采用HanLP即可: http://www.open-open.com/lib/view/open1421978002609.htm /****************** ...
- 关于Java 里的String和对象
之前老师在课堂上讲过关于Java的引用,但是很遗憾,木有认真听啊,所以就在学习Java的过程中迷惑迷惑...最近好像明白一点Java的引用是怎么回事了.以下仅是我个人的理解,如果不对,还请不吝赐教. ...
- JAVA基本类库介绍
我们曾经讲过,Java已经为编程者编制了许多类,这些类已经经过测试,基本上不存在错误,这些类都是我们编程的基础.如果不利用这些已存在的类,我们的 编程工作将变得异常复杂,所以我们应尽可能多的掌握Jav ...
- mysql 卸载 linux
root@localhost ~]# rpm -qa | grep -i mysqlMySQL-client-5.5.52-1.linux2.6.x86_64MySQL-server-5.5.52-1 ...