锁定老帖子 主题:用 haskell 扩展 ruby
精华帖 (6) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-04-10
最后修改:2009-09-09
其编译过程大致是先生成 C 代码(确切的说是 C--),然后使用自带的 gcc 编译。 Haskell 编译后体积小,性能好,不需要笨重的运行时,在 http://shootout.alioth.debian.org/ 的 benchmark 排名经常超过 java 和 D。 与它相似的函数式语言的简单比较如下: 注意:Erlang, Scheme 都是不纯洁的哦。 pure 和 lazy(惰性求值)密切相关。 由于纯函数(就是我们中学课本上的函数,而不是一般编程语言说的“函数”)表现稳定,所以求值后可以存起结果。如果下次调用参数相同,可以直接返回这个结果(似乎编译器的优化要更复杂一些?)。于是纯函数式的语言用惰性求值不会产生任何性能问题。 惰性求值有个好处是可以随心所欲定义无限长的列表,你用到其中某一项时它才会算出那项的值。 eager 的就不行——定义时就会对这个 list 求值,无限循环了。 --------------------------------------------------------------------------------- Haskell vs Ruby: 彻底的函数式 vs 彻底的面向对象,静态类型 vs 动态类型,极度纯洁 vs 极度不纯洁,面向数学公式 vs 面向自然语言,缩进 vs end …… 一方的弱项正好是另一方的长处,所以用 Haskell 扩展 Ruby 还是很有意义的。 当然两者也有一些共同的缺点,譬如:代码量都比较短。 首先请看一个简单的用 Haskell 产生dll的示例: http://www.haskell.org/ghc/docs/latest/html/users_guide/win32-dlls.html 不过 dllMain.c 和各种编译参数太古板了,可以写一个脚本省掉这个过程: makedll.rb 将当前目录下所有.hs文件都检查一遍,生成dllMain.c,然后自动编译链接产生dll。并且连带产生 interface.rb。 ### Begin Config ### Set output dll name #outputdll = 'some name.dll' ### Set ghc options #options = '-O2 -threaded' ### Set heap and stack options #heap_and_stack = '-H128m -K1m' ### End Config if ARGV[0] == 'clean' system 'del *.o *.hi *.c *.a *.h' exit end # tidy config options outputdll ||= "#{File.basename File.expand_path('.')}.dll" options ||= '' heap_and_stack &&= "char *ghc_rts_opts = \"#{heap_and_stack}\";" # hash for haskell-to-C type cast. needs to be complete in the future TypeCast = { 'Int' => 'long', 'Char' => 'char', 'String' => 'char*' } # cast signature from haskell to ruby/dl style def signature_cast line if line =~ /^\s*foreign\s+export\s+stdcall\s+(\w+)\s+\:\:\s+(.+?)\s+\-\>\s+IO\s+(\w+)\s*$/ func = $1.dup ret_type = TypeCast[$3.strip] plist = $2.split('->').map{|e| TypeCast[e.strip] }.join(',') "extern \"#{ret_type} #{func}(#{plist})\", :stdcall" end end # scan files fnames = Dir.entries('.').select do |f| !(File.directory? f) && f =~ /\.hs$/ end modules = [] fnames_with_ext = [] # files containing extern functions rb_interface = [] # ruby interface modules fnames.each do |fn| File.open fn do |f| # search for module xxx module_name = nil while line = f.gets if line =~ /^\s*module (\w+)/ module_name = $1 break end end # search for extern function signatures externs = [] while line = f.gets sig = signature_cast line externs << sig if sig end if externs != [] modules << module_name fnames_with_ext << fn rb_interface << externs.join("\n") end end end # build ruby interface File.open 'interface.rb', 'w' do |f| ruby_template = <<-ES require 'dl/import' require 'dl/types' module %s extend DL::Importer dlload '%s' %s end ES outputdll =~ /^([a-zA-Z]+)/ module_name = $1 module_name[0] = module_name[0].chr.upcase f.puts ruby_template % [module_name, outputdll, rb_interface.join("\n ")] end # build dllMain.c require 'erb' File.open 'dllMain.c', 'w' do |f| f.puts ERB.new(DATA.read).result(binding) end # compile fnames_with_ext.each do |fn| system "ghc -c \"#{fn}\" -fglasgow-exts" end system "ghc -c dllMain.c" # link objs = fnames_with_ext.map do |fn| "\"#{fn.sub /hs$/,'o'}\" \"#{fn.sub /\.hs$/,'_stub.o'}\"" end.join ' ' system "ghc -shared dllMain.o #{objs} -o \"#{outputdll}\" #{options}" __END__ #include <windows.h> #include <Rts.h> <%= heap_and_stack %> <% modules.each do |m| %> extern void __stginit_<%= m %>(void); <% end %> static char* args[] = { "ghcDll", NULL }; BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, void* reserved) { if (reason == DLL_PROCESS_ATTACH) { <% modules.each do |m| %> startupHaskell(1, args, __stginit_<%= m %>); <% end %> return TRUE; } return TRUE; } ------------------------------------------------------------------------------ 简单过程示例: 保证 ghc 6.10.2 (最流行的haskell编译器和解释器) 和 ruby 1.9 (内置DL库)。 建一个文件夹adder,在里面新建 adder.hs,内容如下 module Adder where adder :: Int -> Int -> IO Int –– gratuitous use of IO adder x y = return (x+y) foreign export stdcall adder :: Int -> Int -> IO Int 把上面的 makedll.rb 扔进去,产生 adder.dll 和 interface.rb: ruby makedll.rb ruby makedll.rb clean 用 ruby/DL 调用这个 dll 非常简单: require 'interface.rb' puts "5+8=#{Adder.adder(5,8)}" ruby/DL 调用扩展的好处是可以避免 "dll hell",我的ruby是VC2008编译的,调用gcc产生的动态链接库也不会出现 segfault。 补充1:推荐使用 ghc 6.10.2 , ghc 6.10.1 可能有问题。 补充2:在linux下编译 haskell 共享库要简单一些,不需要dllMain.c。具体可以参看 http://blog.haskell.cz/pivnik/building-a-shared-library-in-haskell/ 补充3:修改 makedll.rb,可以产生 ruby interface 了。 补充4:2009.9 看到 hubris: (只能在 linux 和 mac 下面用,类似于 rubyinline) http://www.infoq.com/news/2009/08/haskell-ruby-hubris http://github.com/mwotton/Hubris/tree/master 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-04-10
最后修改:2009-04-10
并非给ruby吧,只是通过dll import,任何一种语言皆可调用之。dll麻烦是你要了解函数签名,否则就没戏了。
作为面向计算的语言,我倒是有兴趣了解一下haskell和fortran之间的效能关系,尤其是大规模稀疏矩阵和傅里叶变换时候。。。 |
|
返回顶楼 | |
发表时间:2009-04-10
最后修改:2009-04-11
似乎 haskell 不太适合矩阵计算,Immutable Array 的 Update 自然比 Mutable Array 慢,不过有些研究说可以将速度提升到很牛叉的水平。
支持者们总会争辩说性能主要问题在于你的算法复杂度而不是语言…… 然后给一个长长的论文链接说:reading "An approach to fast arrays in Haskell" might be a good idea for everyone wondering about ghc performance 不过我想比较简单的解决方法是调用 fortran 库…… 据我的经验,矩阵运算没有很好的解决方法,譬如 C++ STL 的vararray 号称很快,其实比自己仔细用 C 实现的矩阵慢多了。 哪个语言比较适合 HPC 的讨论: http://lambda-the-ultimate.org/node/2720 另外,其实对公式的表达能力强不代表面向计算…… 考据党(ghc 的老大)对 haskell 的历史的研究(内含全家福一张): http://research.microsoft.com/en-us/um/people/simonpj/papers/history-of-haskell/history.pdf 其中还有比较搞笑的一段: 引用 the program was far more robust than the C program, which often crashed and killed the patient
补充: 当然“慢”和“不适合”是相对的,数学家们就很喜欢 haskell。比 scala 快是肯定的,比起 ruby 更是百倍速度。 我觉得 haskell 扩展比 C 扩展容易写(不用考虑内存分配和很多边界条件),性能也很不错。如果用基于 VM 的语言,得通过两层接口,还不如 socket 调用。 并发方面, haskell 大概比 erlang 弱(http://thinkerlang.com/2006/01/01/haskell-vs-erlang-reloaded.html): 引用 Concurrency in Haskell deserves a praise, specially when used together with STM. Threads are lightweight (1024 bytes on the heap) and easy to launch and STM is a beautiful thing. Nothing beats being able to just send yourself a message, though. This is something that you can easily do with Erlang.
Erlang processes (327 bytes starting up, including heap) come with a message queue and you retrieve messages with “selective receive” that uses the same pattern-matching facilities as everything else. |
|
返回顶楼 | |
发表时间:2009-06-11
night_stalker,haskell是否可以写成so文件给ruby调用。
|
|
返回顶楼 | |
发表时间:2009-06-11
最后修改:2009-06-11
CharlesCui 写道 night_stalker,haskell是否可以写成so文件给ruby调用。
理论上可行 …… 但还没试过。 前提是:你的 ruby 是 gcc 编译的。 需要先把 ruby.h 等头文件转一遍,可能这个工具有用: http://freshmeat.net/projects/c2hs 但是宏转不了。得自己对着理一遍 …… 有了接口,然后就和写 ruby 的 C-extension 差不多: 定义入口函数、入口函数里定义一些模块或者类、将 haskell 函数映射到 ruby 函数。 最后编译链接参数,要加上 ruby 的 lib。 总之…… 很麻烦。比用 D 语言写 ruby extension 麻烦。所以我觉得最适当的方法是做成通用 dll。 另外,可以用 Haskell 做 fcgi server,配合 rails ,参见上面某捷克文博客…… |
|
返回顶楼 | |
发表时间:2009-06-11
我还是觉得 ocaml 和 f#的 syntax更对些胃口。
只是 ocaml利用multi-core不如 haskell来得方便 |
|
返回顶楼 | |
发表时间:2009-06-11
其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...
|
|
返回顶楼 | |
发表时间:2009-06-12
最后修改:2009-06-12
ray_linn 写道 其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...
(+﹏+)~,不够方便是个问题 …… 再写个小工具生成接口就好了。 Hia hia: |
|
返回顶楼 | |
发表时间:2009-06-12
night_stalker 写道 ray_linn 写道 其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...
(+﹏+)~,不够方便是个问题 …… 再写个小工具生成接口就好了。 恩,我想到的是,用C#同时可以调用ghc和fortran两个的dll,同时用C#的GDI+应该可以做很多事情。 ghc负责函数, fortran 负责 矩阵与傅里叶 C# 负责绘图 |
|
返回顶楼 | |
发表时间:2009-06-12
哇咔咔,修改后的版本自动产生 interface.rb, 还是 Ruby 方便吧?
|
|
返回顶楼 | |