Julia 是个灵活的动态语言,适合科学和数值计算,性能可与传统静态类型语言媲美。基本上长这个样子。
两种运行方法一种是交互式,如:
另一种是解释式的,如julia hello.jl
Julia的主要特点有:核心语言较小;标准库是用Julia 本身写的,包括整数运算在内的基础运算。拥有完善的类型,方便构造对象和类型声明。基于参数类型进行函数重载。参数类型不同,自动生成高效、专用的代码。性能高,接近静态编译语言。不需封装或API而直接调用C函数。
最重要的是,为并行和分布式计算而设计的Julia,本身对并行有良好的支持。
并行计算的基本思想就是把复杂的工作进行分解,分解成可以同时进行的多个子任务,来缩短任务的完成时间,提升系统的吞吐性能。单线程独占模式下的程序需要等待一个任务完成,才会启动另一个任务。如果一个任务在进行大量的I\O操作,其他任务也没有办法开始,只能等待。而这时CPU就会空闲。所以多线程并发就表现出了很好的资源利用能力,一个线程在I\O操作,另一个就可以利用CPU(或其他计算)资源。
Julia提供了一个基于消息传递的多进程环境,能够同时在多处理器上使用独立的内存空间运行程序。这个内存空间由每个CPU单独控制,他们之间通过内部消息机制来通信。Julia的消息机制不同于MPI,并不是收发,而是看起来更像函数调用的方式。
并行程序的两个要素远程引用和远程调用:远程引用是引用其他特定处理器的对象,这个引用可被其它任何处理器访问。远程调用是某处理器调用其它(或自身)处理器。远端调用的结果放回远程引用,可以使用fetch来抓取结果。例如:r=remote_call(2,rand,2,2)这个远程调用中,第一个参数为核心序号,第二个参数为调用的函数名,其后为该函数rand的参数。获取该结果为fetch(r)。
而宏@spawnat用来进行调用,更为方便。如 @spawnat2 1+fetch(r)在第二个核心上进行加1操作。如果要立即获取结果,请使用remote_call_fetch(2, ref, r, 1, 1)。
为了保证程序在所有核心上都可以使用,建议使用require("myfile")的方法导入文件。了解当前系统核数的情况,请采用np = nprocs()方法。
使用它并行程序做运算,我们需要:合理地把复杂任务进行拆解,考虑拆解后的并行子任务的个数和机器处理核的个数关系,以及每个子任务处理的时间,对之上的数据进行平衡处理。提升比较明显的是那种处理的数据量很大,或者要执行的数据处理任务繁重,并且这些任务本身就可以分解为互不相关的子任务。
随之而来的问题是:开发复杂性增高,需要考虑子任务的协调,以及彼此间的通信,而且还要基于机器性能考虑开线程个数。
Julia是采用远程调用和动态数组的方式来解决这些问题。现在举例说明。
北京市有3723条公交路线(包括上下行)。现需求根据本月历史数据,计算每条线路的平均运行时间。本实验中,我们展示采用随机生成数据。实验环境是:3GHz*4core CPU,4GB内存,带GPU卡,Linux操作系统。采用的Julia版本为0.1.2。
Julia的并行解决办法,是将该问题分解为平均1000条公交线的4个数据集,交给不同的CPU内核来运算。首先我们定义一个最内部求平均运行时间的函数。
functionzm(a::Array,x::Int32,y::Int32,z::Int32)
b=reshape(a,x,y,z)
sum=Array(Float32,x,y)
fori=1:x
forj=1:y
fork=1:z sum[i,j]= sum[i,j] + b[i,j,k] end
end
end
sum/z
end
该函数返回求出的平均数。该平均数结果为二维数组。
现在编写主程序,在主文件里生成三维随机数数组,他们的值为1。该随机数组为4000*40*100,代表4000条线路上的40个站点每天100趟的车次信息。程序主体如下:
#importall Base #需导入该包
const X=4000; const Y=40; const Z=200;const NP=4; XX=1000 #定义常量
busIni = Array(Int32,X,Y,Z) #定义数据
fill!(busIni,1) #填充为1
busInter = Array(Int32,X,Y,Z) #定义中间计算结果数组
addprocs_local(3) #增加并行运算的核数,增加三个,就总共为四个核参与运算。
pastart=time() #计时开始
pbInter1=busInter[1:XX,1:Y,1:Z] #拆分数组
pbInter2=busInter[XX+1:2XX,1:Y,1:Z]
pbInter3=busInter[2XX+1:3XX,1:Y,1:Z]
pbInter4=busInter[3XX+1:X,1:Y,1:Z]
require("zm.jl") #导入ZM函数
rzm1=remote_call(1,zm,pbInter1,XX,Y,Z) #在1号核上调用zm函数,传入的值为拆分的第一个数组。
rzm2=remote_call(2,zm,pbInter2,XX,Y,Z)
rzm3=remote_call(3,zm,pbInter3,XX,Y,Z)
rzm4=remote_call(4,zm,pbInter4,XX,Y,Z)
pbInter=vcat(fetch(rzm1),fetch(rzm2),fetch(rzm3),fetch(rzm4)) #取回数据并组合
println("耗时", time()-pastart, "s") #显示计算时间
在上述实验配置下,不同规模运行情况
表1 运行时间对比
|
串行时间 |
并行时间 |
加速比 |
1600000000 |
11.2 |
3.1 |
3.61 |
下一步可能的研究和优化点
1.进一步提高数据划分和组合的效率,减少IO时间。
2.优化和提高并行算法,使其更搞笑。
3.开发通用并行算法程序库。
Julia为开发和运行并行程序、解决大规模数据运算问题提供了方便快捷、功能强大的语言工具。构建基于Julia和高性能计算机的云平台,将高性能计算机的计算能力通过服务的方式提供给异地客户,将是一个很有前景的应用。
参考
1. Julia主页
2. Ubuntu下安装Julia
3. Julia运行程序
4. 第一个Julia程序