乐筑天下

搜索
欢迎各位开发者和用户入驻本平台 尊重版权,从我做起,拒绝盗版,拒绝倒卖 签到、发布资源、邀请好友注册,可以获得银币 请注意保管好自己的密码,避免账户资金被盗
查看: 110|回复: 6

Kean专题(4)—Concurrent_Programming

[复制链接]

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 09:40:00 | 显示全部楼层 |阅读模式
一、在使用异步工作流简化并发程序设计(F#)
January 25, 2008
Using F# Asynchronous Workflows to simplify concurrent programming in AutoCAD
In the last post we saw some code that downloaded data - serially - from a number of websites via RSS and created AutoCAD entities linking to the various posts.
As promised, in today's post we take that code and enable it to query the same data in parallel by using Asynchronous Workflows in F#. Asynchronous Workflows are an easy-to-use yet powerful mechanism for enabling concurrent programming in F#.
Firstly, a little background as to why this type of technique is important. As many - if not all - of you are aware, the days of raw processor speed doubling every couple of years are over. The technical innovations that enabled  Moore's Law to hold true for half a century are - at least in the area of silicon-based microprocessor design - hitting a wall (it's apparently called the Laws of Physics :-). Barring some disruptive technological development, the future gains in computing performance are to be found in the use of parallel processing, whether via multiple cores, processors or distributed clouds of computing resources.
Additionally, with an increasing focus on distributed computing and information resources, managing tasks asynchronously becomes more important, as information requests across a network inevitably introduce a latency that can be mitigated by the tasks being run in parallel.
The big problem is that concurrent programming is - for the most-part - extremely difficult to do, and even harder to retro-fit into existing applications. Traditional lock-based parallelism (where locks are used to control access to shared computing resources) is both unwieldy and prone to blocking. New technologies, such as Asynchronous Workflows and Software Transactional Memory, provide considerable hope (and this is a topic I have on my list to cover at some future point).
Today's post looks at a relatively simple scenario, in the sense that we want to perform a set of discrete tasks in parallel, harnessing those fancy multi-core systems for those of you lucky enough to have them (I'm hoping to get one when I next replace my notebook, sometime in March), but that these tasks are indeed independent: we want to wait until they are all complete, but we do not have the additional burden of them communicating amongst themselves or using shared resources (e.g. accessing shared memory) during their execution.
We are also going to be very careful only to run parallel tasks unrelated to AutoCAD. Any access made into AutoCAD's database, for instance, needs to be performed in series: AutoCAD is not thread-safe when it comes to the vast majority of its programmatically-accessible functionality. So we're going to run a set of asynchronous, parallel tasks to query our various RSS feeds, and combine the results before creating the corresponding geometry in AutoCAD. This all sounds very complex, but the good (actually great) news is that Asynchronous Workflows does all the heavy lifting. Phew.
Here's the modified F# code
  1. // Use lightweight F# syntax
  2. #light
  3. // Declare a specific namespace and module name
  4. module MyNamespace.MyApplication
  5. // Import managed assemblies
  6. #I @"C:\Program Files\Autodesk\AutoCAD 2008"
  7. #r "acdbmgd.dll"
  8. #r "acmgd.dll"
  9. open Autodesk.AutoCAD.Runtime
  10. open Autodesk.AutoCAD.ApplicationServices
  11. open Autodesk.AutoCAD.DatabaseServices
  12. open Autodesk.AutoCAD.Geometry
  13. open System.Xml
  14. open System.Collections
  15. open System.Collections.Generic
  16. open System.IO
  17. open System.Net
  18. open Microsoft.FSharp.Control.CommonExtensions
  19. // The RSS feeds we wish to get. The first two values are
  20. // only used if our code is not able to parse the feed's XML
  21. let feeds =
  22.   [ ("Through the Interface",
  23.      "http://blogs.autodesk.com/through-the-interface",
  24.      "http://through-the-interface.typepad.com/through_the_interface/rss.xml");
  25.      
  26.     ("Don Syme's F# blog",
  27.      "http://blogs.msdn.com/dsyme/",
  28.      "http://blogs.msdn.com/dsyme/rss.xml");
  29.    
  30.     ("Shaan Hurley's Between the Lines",
  31.      "http://autodesk.blogs.com/between_the_lines",
  32.      "http://autodesk.blogs.com/between_the_lines/rss.xml");
  33.    
  34.     ("Scott Sheppard's It's Alive in the Lab",
  35.      "http://blogs.autodesk.com/labs",
  36.      "http://labs.blogs.com/its_alive_in_the_lab/rss.xml");
  37.    
  38.     ("Lynn Allen's Blog",
  39.      "http://blogs.autodesk.com/lynn",
  40.      "http://lynn.blogs.com/lynn_allens_blog/index.rdf");
  41.     ("Heidi Hewett's AutoCAD Insider",
  42.      "http://blogs.autodesk.com/autocadinsider",
  43.      "http://heidihewett.blogs.com/my_weblog/index.rdf") ]
  44. // Fetch the contents of a web page, asynchronously
  45. let httpAsync(url:string) =
  46.   async { let req = WebRequest.Create(url)
  47.           use! resp = req.GetResponseAsync()
  48.           use stream = resp.GetResponseStream()
  49.           use reader = new StreamReader(stream)
  50.           return reader.ReadToEnd() }
  51. // Load an RSS feed's contents into an XML document object
  52. // and use it to extract the titles and their links
  53. // Hopefully these always match (this could be coded more
  54. // defensively)
  55. let titlesAndLinks (name, url, xml) =
  56.   let xdoc = new XmlDocument()
  57.   xdoc.LoadXml(xml)
  58.   let titles =
  59.     [ for n in xdoc.SelectNodes("//*[name()='title']")
  60.         -> n.InnerText ]
  61.   let links =
  62.     [ for n in xdoc.SelectNodes("//*[name()='link']") ->
  63.         let inn = n.InnerText
  64.         if  inn.Length > 0 then
  65.           inn
  66.         else
  67.           let href = n.Attributes.GetNamedItem("href").Value
  68.           let rel = n.Attributes.GetNamedItem("rel").Value
  69.           if href.Contains("feedburner") then
  70.               ""
  71.           else
  72.             href ]
  73.          
  74.   let descs =
  75.     [ for n in xdoc.SelectNodes
  76.         ("//*[name()='description' or name()='content' or name()='subtitle']")
  77.           -> n.InnerText ]
  78.   // A local function to filter out duplicate entries in
  79.   // a list, maintaining their current order.
  80.   // Another way would be to use:
  81.   //    Set.of_list lst |> Set.to_list
  82.   // but that results in a sorted (probably reordered) list.
  83.   let rec nub lst =
  84.     match lst with
  85.     | a::[] -> [a]
  86.     | a::b ->
  87.       if a = List.hd b then
  88.         nub b
  89.       else
  90.         a::nub b
  91.     | [] -> []
  92.   // Filter the links to get (hopefully) the same number
  93.   // and order as the titles and descriptions
  94.   let real = List.filter (fun (x:string) -> x.Length > 0)  
  95.   let lnks = real links |> nub
  96.   // Return a link to the overall blog, if we don't have
  97.   // the same numbers of titles, links and descriptions
  98.   let lnum = List.length lnks
  99.   let tnum = List.length titles
  100.   let dnum = List.length descs
  101.   
  102.   if tnum = 0 || lnum = 0 || lnum  tnum || dnum  tnum then
  103.     [(name,url,url)]
  104.   else
  105.     List.zip3 titles lnks descs
  106. // For a particular (name,url) pair,
  107. // create an AutoCAD HyperLink object
  108. let hyperlink (name,url,desc) =
  109.   let hl = new HyperLink()
  110.   hl.Name ]
  111. let createHyperlinksFromRss() =
  112.   
  113.   // Let's get the usual helpful AutoCAD objects
  114.   
  115.   let doc =
  116.     Application.DocumentManager.MdiActiveDocument
  117.   let db = doc.Database
  118.   // "use" has the same effect as "using" in C#
  119.   use tr =
  120.     db.TransactionManager.StartTransaction()
  121.   // Get appropriately-typed BlockTable and BTRs
  122.   let bt =
  123.     tr.GetObject
  124.       (db.BlockTableId,OpenMode.ForRead)
  125.     :?> BlockTable
  126.   let ms =
  127.     tr.GetObject
  128.       (bt.[BlockTableRecord.ModelSpace],
  129.        OpenMode.ForWrite)
  130.     :?> BlockTableRecord
  131.   
  132.   // Add text objects linking to the provided list of
  133.   // HyperLinks, starting at the specified location
  134.   
  135.   // Note the valid use of tr and ms, as they are in scope
  136.   let addTextObjects pt lst =
  137.     // Use a for loop, as we care about the index to
  138.     // position the various text items
  139.     let len = List.length lst
  140.     for index = 0 to len - 1 do
  141.       let txt = new DBText()
  142.       let (name:string,hl:HyperLink) = List.nth lst index
  143.       txt.TextString  ignore
  144.       tr.AddNewlyCreatedDBObject(txt,true)
  145.       txt.Hyperlinks.Add(hl) |> ignore
  146.   // Here's where we do the real work, by firing
  147.   // off - and coordinating - asynchronous tasks
  148.   // to create HyperLink objects for all our posts
  149.   let links =
  150.     Async.Run
  151.       (Async.Parallel
  152.         [ for (name,url,feed) in feeds ->
  153.           hyperlinksAsync (name,url,feed) ])
  154.   // Add the resulting objects to the model-space  
  155.   let len = Array.length links
  156.   for index = 0 to len - 1 do
  157.     // This is where you can adjust:
  158.     //  the column spacing (x value)
  159.     //  the vertical offset from origin (y axis)
  160.     let pt =
  161.       new Point3d
  162.         (15.0 * (Int32.to_float index),
  163.          30.0,
  164.          0.0)
  165.     addTextObjects pt (Array.get links index)
  166.   tr.Commit()
A few comments on the changes:
Lines 57-62 define our new httpAsync() function, which uses GetResponseAsync() - a function exposed in F# 1.9.3.7 - to download the contents of a web-page asynchronously [and which I stole shamelessly from Don Syme, who presented the code last summer at Microsoft's TechEd].
Lines 141-144 define another asynchronous function, hyperlinksAsync(), which calls httpAsync() and then - as before - extracts the feed information and creates a corresponding list of HyperLinks. This is significant: creation of AutoCAD HyperLink objects will be done on parallel; it is the addition of these objects to the drawing database that needs to be performed serially.
Lines 214-217 replace our very simple "map" with something slightly more complex: this code runs a list of tasks in parallel and waits for them all to complete before continuing. What is especially cool about this implementation is the fact that exceptions in individual tasks result in the overall task failing (a good thing, believe it or not :-), and the remaining tasks being terminated gracefully.
Lines 221 and 233 change our code to handle an array, rather than a list (while "map" previously returned a list, Async.Run returns an array).
When run, the code creates exactly the same thing as last time (although there are a few more posts in some of the blogs ;-)

pttnzcjp4kr.png

pttnzcjp4kr.png

A quick word on timing: I used "F# Interactive" to do a little benchmarking on my system, and even though it's single-core, single-processor, there was a considerable difference between the two implementations. I'll talk more about F# Interactive at some point, but think of it to F# in Visual Studio as the command-line is to LISP in AutoCAD: you can very easily test out fragments of F#, either by entering them directly into the F# Interactive window or highlighting them in Visual Studio's text editor and hitting Alt-Enter.
To enable function timing I entered "#time;;" (without the quotations marks) in the F# Interactive window. I then selected and loaded the supporting functions needed for each test - not including the code that adds the DBText objects with their HyperLinks to the database, as we're only in Visual Studio, not inside AutoCAD - and executed the "let links = ..." assignment in our two implementations of the createHyperlinksFromRss() function (i.e. the RSS command). These functions do create lists of AutoCAD HyperLinks, but that's OK: this is something works even outside AutoCAD, although we wouldn't be able to do anything much with them. Also, the fact we're not including the addition of the entities to the AutoCAD database is not relevant: by then we should have identical data in both versions, which would be added in exactly the same way.
Here are the results:复制代码
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 10:15:00 | 显示全部楼层
二、用多义线路径模拟Hatch
February 21, 2008
Parallelizing robotic AutoCAD hatching with F# and .NET
In the last post we saw some code combining F# with C# to create a random "hatch" - a polyline path that bounces around within a boundary.
This post extends the code to make use of Asynchronous Workflows in F# to parallelize the testing of points along a segment. In the initial design of the application I decided to test 10 points along each segment, to see whether it remained entirely within our boundary: the idea being that this granularity makes it very likely the segment will fail the test, should it happen to leave the boundary at any point. Not 100% guaranteed, but a high probability event. What this code does is take the 10 tests and queue them up for concurrent processing (where the system is capable of it).
Asynchronous Workflows - as suggested by the name - were intended to fire off and manage asynchronous tasks (ones that access network resources, for instance). The segment testing activity is actually very local and processor-bound, so it's not really what the mechanism was intended for, but I thought it would be interesting to try. One interesting point: while testing this code I noticed that it actually ran slower on a single processor machine, which is actually quite logical: only one core is available for processing, so the amount of sequential processing is not reduced but the overhead of synchronizing the various tasks is added. So it was fairly inevitable it would take longer. In the post I first talked about Asynchronous Workflows I showed a sample that queried multiple RSS sites for data: even on a single processor machine this was significantly quicker, as parallelizing the network latency led to a clear gain.
Anyway, as it was slower on this machine I decided only to enable the parallel version of the code in cases where the computer's NUMBER_OF_PROCESSORS environment variable is greater than 1. I checked quickly on a colleague's dual-core machine, and sure enough this variable is set to 2 on his system. I haven't, however, tested the code on a dual- or multi-core system, but I'll be getting a new system in a matter of weeks, which will give me the chance to test it out.
Here's the the complete project which defines both FB and FBA commands for simple side-by-side comparison.
请点击此处下载

请先注册会员后在进行下载

已注册会员,请先登录后下载

文件名称:cdps42qrbsz.zip 
下载次数:0  文件大小:11.62 KB  售价:2银币 [记录]
下载权限: 不限 以上或 Vip会员   [开通Vip]   [签到领银币]  [免费赚银币]

回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 10:24:00 | 显示全部楼层
三、编程语言分类
March 17, 2008
A simple taxonomy of programming languages
Someone asked me recently how I categorize different programming paradigms. I thought it was a very interesting question, so here's what I responded. Please bear in mind that this is very much the way I see things, and is neither an exhaustive nor a formally-ratified taxonomy.
One way to look at languages is whether they're declarative or imperative:
Declarative programming languages map the way things are by building up “truths”: this category includes  functional programming languages (such as  Miranda,  Haskell and  Erlang) which tend to be mathematical in nature (you define equations) and start with  lambda calculus as a foundation. The other main set of declarative languages are  logic programming languages (such as  Prolog), which start with  propositional calculus as a foundation (you declare axioms that build up to a system against which you can run queries). Declarative languages tend to focus on describing the problem to solve, rather than how to solve it.
Imperative programming languages, on the other hand, are lists of instructions of what to do: I tend to consider  procedural programming languages (such as  C,  COBOL,  Fortran and  Pascal) as a sub-category which focus on the definition and execution of sub-routines, while some people treat the terms imperative and procedural as synonyms.
Considering these definitions,  object-oriented programming languages (such as  Smalltalk and  Eiffel) should probably be considered declarative, as conceptually they map real-world objects, but the truth is that the most popular OO languages (such as  C++) are impure, and so most OO systems combine big chunks of procedural (i.e. imperative) code. Many people who think they’re doing OOP are actually packaging up procedures.
Note that I've tried not to list multi-paradigm languages such as  Ada,  C++ and  F# in the above categorisation. It's possible that some of the languages I've listed are also multi-paradigm, but anyway.
One other way to think about languages is whether they’re top-down or bottom-up:
Bottom-up languages are ultimately layered on how a processor works (from machine code to assembly language to C & C++), while top-down languages start from the world of mathematics and logic and add language features that allow them to be used for programming (i.e. declarative languages are therefore top-down). This latter set of languages are starting to see increased adoption, as they assume much less (even nothing) about the underlying machinery, in which big changes are occurring with multiple processing cores being introduced (which essentially invalidate the assumptions of previous generations of programmers, who have been conditioned to think in terms of the processor's ability to store and access state).
Many popular - or soon to be popular - programming environments are pragmatic in nature: C++ allows OOP but can also be used for procedural programming, VB.NET now allows you to define and access objects while coming from a long line of procedural languages, F# is multi-paradigm, combining OO with functional and imperative programming.
There are bound to be people with differing views on this subject (and many of them are no doubt more intelligent and experienced in these matters than I), but this is how I would answer the question of how to categorise programming languages.
For those of you with an interest in the future of programming languages, I can strongly recommend the following Channel 9 episodes. If you're not aware of Channel 9, then prepare to be impressed: Microsoft has given a fantastic gift to the development community with this resource.
Burton Smith: On General Purpose Super Computing and the History and Future of Parallelism
Erik Meijer: Functional Programming
Anders Hejlsberg, Herb Sutter, Erik Meijer, Brian Beckman: Software Composability and the Future of Languages
Brian Beckman: Don't fear the Monads
Joe Armstrong - On Erlang, OO, Concurrency, Shared State and the Future, Part 1
Joe Armstrong - On Erlang, OO, Concurrency, Shared State and the Future, Part 2
Enjoy! :-)
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 10:31:00 | 显示全部楼层
四、销毁AutoCAD对象的时机和方法
June 16, 2008
Cleaning up after yourself: how and when to dispose of AutoCAD objects in .NET
A question came up recently in an internal discussion and I thought I'd share it as it proved so illuminating.
If I have an object of a type which implements IDisposable, is it good practice to explicitly dispose it (whether via the using statement or calling Dispose() explicitly)?
The quick(ish) answer is:
Yes it is, but sometimes you might choose not to as the increase in code simplicity outweighs the benefits derived from manually disposing of the objects.
So, naturally, the devil is in the detail. Let's take a look at the three scenarios where you're likely to be working with IDisposable objects inside AutoCAD:
Temporary objects - such as those provided by Autodesk.AutoCAD.Geometry - which are never Database-resident
Temporary objects with the potential to be database-resident but which never actually get added to a Database
Database-resident objects added/accessed via a Transaction
Below follows the details on each of these categories.
Temporary objects of types not derived from DBObject
The first category of temporary objects, such as Geometry.Line, are safe to be disposed of either "manually" (by your own code) or "automatically" (by the .NET garbage collector).
Temporary objects of DBObject-derived types
The second category of temporary objects, which are of a type derived from DBObject, must be disposed of manually. It is absolutely unsafe not to dispose of objects of DBObject-derived classes from the main execution thread in AutoCAD.
Why is this the case? Firstly, the majority of the classes available through AutoCAD's .NET API are currently unmanaged, with a relatively thin wrapper exposing them to the .NET world. Inside AutoCAD, all Database-resident objects are managed by a single-threaded runtime component, AcDb (which, along with some other components, is productized as Autodesk RealDWG). A side note: if you're using ObjectARX or RealDWG from C++, don't be confused by the fact your project's C-runtime memory management is likely to be "Multi-threaded DLL", RealDWG is not thread-aware and so must be treated as single-threaded, for all intents and purposes.
And - secondly - on to the reason that automatic garbage collection is not to be trusted on DBObject-derived types: the .NET garbage collector runs on a separate, typically low-priority - unless memory is running short - thread. So if a DBObject-derived type is garbage-collected inside AutoCAD, a separate thread will essentially call into a non thread-safe component (RealDWG), which is very, very likely to cause AutoCAD to crash.
So you must always call Dispose() on temporary objects of DBObject-derived classes in your .NET code - you cannot rely on the CLR to manage your objects'  lifetimes for you.
Interestingly, this is also the reason why the F# code I posted in this prior post will not effectively leverage multiple cores (something I've just tested since getting a multi-core machine). We are using a Ray (which is a DBObject-derived class) to get intersection points with our path, and then disposing of this temporary object from an arbitrary thread (farmed off via F# Asynchronous Workflows). So this is unsafe, and won't run for long before crashing. At least now I understand why.
Database-resident, Transaction-managed objects
The third category of objects are those we use a Transaction either to add to the Database or to open for access. The good news is that - as the Transaction is aware of the objects it manages (via calls to AddNewlyCreatedDBObject() or to GetObject()), it is able to dispose of them automatically when it, itself, is disposed. So the key here is to make sure you wrap your use of Transactions in using blocks or call Dispose() on them when you're done. There is no need to explicitly dispose of the objects managed by a Transaction (unless there is a failure between the time the object is created and when it is added to the transaction via AddNewlyCreatedDBObject(), of course).
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 10:37:00 | 显示全部楼层
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 10:55:00 | 显示全部楼层
六、一个简单的注标工具(?)
January 28, 2009
Implementing a simple graphing tool inside AutoCAD using F#
Well, I couldn't resist... as I mentioned in the last post - where we looked at creating a simple graph inside AutoCAD as an example of modifying objects inside nested transactions - the idea of graphing inside AutoCAD is a good fit for F#. This is for a number of reasons: F# is very mathematical in nature and excels at processing lists of data. I also spiced it up a bit by adding some code to parallelise some of the mathematical operations, but that didn't turn out to be especially compelling with my dual-core laptop. More on that later.
Here's the F# code:
  1. // Use lightweight F# syntax
  2. #light
  3. // Declare a specific namespace and module name
  4. module Grapher.Commands
  5. // Import managed assemblies
  6. open Autodesk.AutoCAD.Runtime
  7. open Autodesk.AutoCAD.ApplicationServices
  8. open Autodesk.AutoCAD.DatabaseServices
  9. open Autodesk.AutoCAD.Geometry
  10. // Define a common normalization function which makes sure
  11. // our graph gets mapped to our grid
  12. let normalize fn normFn x minInp maxInp maxOut =
  13.   let res =
  14.     fn ((maxInp - minInp) * x / maxOut)
  15.   let normRes = normFn res
  16.   if normRes >= 0.0 && normRes ]
  17. let gridCommand() =
  18.   // We'll time the command, so we can check the
  19.   // sync vs. async efficiency
  20.   let starttime = System.DateTime.Now
  21.   // Let's get the usual helpful AutoCAD objects
  22.   let doc =
  23.     Application.DocumentManager.MdiActiveDocument
  24.   let ed = doc.Editor
  25.   let db = doc.Database
  26.   // "use" has the same effect as "using" in C#
  27.   use tr =
  28.     db.TransactionManager.StartTransaction()
  29.   // Get appropriately-typed BlockTable and BTRs
  30.   let bt =
  31.     tr.GetObject
  32.       (db.BlockTableId,OpenMode.ForRead)
  33.     :?> BlockTable
  34.   let ms =
  35.     tr.GetObject
  36.       (bt.[BlockTableRecord.ModelSpace],
  37.        OpenMode.ForWrite)
  38.     :?> BlockTableRecord
  39.   // Function to create a filled circle (hatch) at a
  40.   // specific location
  41.   // Note the valid use of tr and ms, as they are in scope
  42.   let createCircle pt rad =
  43.     let hat = new Hatch()
  44.     hat.SetDatabaseDefaults();
  45.     hat.SetHatchPattern
  46.       (HatchPatternType.PreDefined,
  47.        "SOLID")
  48.     let id = ms.AppendEntity(hat)
  49.     tr.AddNewlyCreatedDBObject(hat, true)
  50.     // Now we create the loop, which we make db-resident
  51.     // (appending a transient loop caused problems, so
  52.     // we're going to use the circle and then erase it)
  53.     let cir = new Circle()
  54.     cir.Radius  ignore
  55.     hat.AppendLoop(HatchLoopTypes.Default, loops)
  56.     hat.EvaluateHatch(true)
  57.     // Now we erase the loop
  58.     cir.Erase()
  59.     id
  60.   // Function to create our grid of circles
  61.   let createGrid size rad offset =
  62.     let ids = new ObjectIdCollection()
  63.     for i = 0 to size - 1 do
  64.       for j = 0 to size - 1 do
  65.         let pt =
  66.           new Point3d
  67.             (offset * (Int32.to_float i),
  68.             offset * (Int32.to_float j),
  69.             0.0)
  70.         let id = createCircle pt rad
  71.         ids.Add(id) |> ignore
  72.     ids
  73.   // Function to change the colour of an entity
  74.   let changeColour col (id : ObjectId) =
  75.     if id.IsValid then
  76.       let ent =
  77.         tr.GetObject(id, OpenMode.ForWrite) :?> Entity
  78.       ent.ColorIndex = 0 then
  79.         (i * size) + res
  80.     else
  81.         -1
  82.   // Apply our function synchronously for each value of x
  83.   let applySyncBelowMax size fn =
  84.     [| for i in [0..size-1] ->
  85.        getIndex fn size i |]
  86.   // Apply our function asynchronously for each value of x
  87.   let applyAsyncBelowMax size fn =
  88.     Async.Run
  89.       (Async.Parallel
  90.         [ for i in [0..size-1] ->
  91.           async { return getIndex fn size i } ])
  92.   // Hardcode the size of the grid and create it
  93.   let size = 50
  94.   let ids = createGrid size 0.5 1.2
  95.   // Make the circles all red to start with
  96.   Seq.iter makeRed (Seq.cast ids)
  97.   // From a certain index in the list, get an object ID
  98.   let getId i =
  99.     if i >= 0 then
  100.       ids.[i]
  101.     else
  102.       ObjectId.Null
  103.   // Apply one of our trig functions, synchronously or
  104.   // otherwise, to our grid
  105.   applySyncBelowMax size normSin |>
  106.     Array.map getId |>
  107.       Array.iter makeYellow
  108.   // Commit the transaction
  109.   tr.Commit()
  110.   // Check how long it took
  111.   let elapsed =
  112.       System.DateTime.op_Subtraction
  113.         (System.DateTime.Now, starttime)
  114.   ed.WriteMessage
  115.     ("\nElapsed time: " +
  116.     elapsed.ToString())
Here's what you see on AutoCAD's drawing canvas when you run the GRAPH command as it stands:

glkq3g02x1y.png

glkq3g02x1y.png


If you want to play around with other functions, you can edit the call to applySyncBelowMax to pass normCos or normTan instead of normSin.

52le4ef4m2z.png

52le4ef4m2z.png


fsemrlqryvx.png

fsemrlqryvx.png


As I mentioned earlier, if you swap the call to be applyAsyncBelowMax instead of applySyncBelowMax you will actually run the mathematics piece as asynchronous tasks. These are CPU-bound operations - they don't call across the network or write to a hard-drive, which might have increased the benefit of calling them asynchronously - so right now the async version actually runs more slowly than the sync version. If I were to have more processing cores available to me, it might also give us more benefit, but right now with my dual-core machine there's more effort spent coordinating the tasks than you gain from the parallelism. But I'll let you play around with that yourselves... you may get better results. One other note on that piece of the code: at some point I'd like to make use of the Parallel Extensions for .NET (in particular the Task Parallel Library (TPL)), but for now I've continued with what I know, the asynchronous worklows capability which is now standard in F#.
I'm travelling in India this week (and working from our Bangalore office next week), so this is likely to be my last post of the week.
回复

使用道具 举报

72

主题

2726

帖子

9

银币

社区元老

Rank: 75Rank: 75Rank: 75

铜币
3014
发表于 2009-5-17 11:01:00 | 显示全部楼层

七、并行像素化
February 02, 2009
Parallelized pixelization inside AutoCAD using F#
As promised in the last post, we're now going to look at how to change the code to make the colour averaging routine work in parallel. The overall performance is marginally better on my dual-core machine, but I fully expect it to get quicker and quicker as the number of cores multiply.
To start with, though, here's the modified "synchronous" version of the code - as I went through making the code work in parallel, I noticed a bunch of general enhancements that were applicable to both versions. Here's the updated F# code:
  1. // Use lightweight F# syntax
  2. #light
  3. // Declare a specific namespace and module name
  4. module SyncPixelizer.Commands
  5. // Import managed assemblies
  6. #nowarn "9" // ... because we're using NativePtr
  7. open Autodesk.AutoCAD.Runtime
  8. open Autodesk.AutoCAD.ApplicationServices
  9. open Autodesk.AutoCAD.DatabaseServices
  10. open Autodesk.AutoCAD.EditorInput
  11. open Autodesk.AutoCAD.Geometry
  12. open Autodesk.AutoCAD.Colors
  13. open System.Drawing.Imaging
  14. open Microsoft.FSharp.NativeInterop
  15. // Add up the RGB values of a list of pixels
  16. // We use a recursive function with an accumulator argument,
  17. // (rt, gt, bt), to allow tail call optimization
  18. let rec sumColors (pix : List) (rt,gt,bt) =
  19.   match pix with
  20.     | [] -> (rt, gt, bt)
  21.     | (r, g, b) :: tl ->
  22.         sumColors tl
  23.           (rt + Byte.to_int r,
  24.           gt + Byte.to_int g,
  25.           bt + Byte.to_int b)
  26. // Average out the RGB values of a list of pixels
  27. let getAverageColour (pixels : List) =
  28.   let (rsum, gsum, bsum) =
  29.     sumColors pixels (0, 0, 0)
  30.   let count = pixels.Length
  31.   let ravg = Byte.of_int (rsum / count)
  32.   let gavg = Byte.of_int (gsum / count)
  33.   let bavg = Byte.of_int (bsum / count)
  34.   // For some reason the pixel needs ro be reversed - probably
  35.   // because of the bitmap format (needs investigation)
  36.   Color.FromRgb(bavg, gavg, ravg)
  37. // Function to get an index into our flat array
  38. // from an x,y pair
  39. let getIndexFromXY ysize x y =
  40.   (x * ysize) + y
  41. //  Get a chunk of pixels to average from one row
  42. // We use a recursive function with an accumulator argument
  43. // to allow tail call optimization
  44. let rec getChunkRowPixels p xsamp acc =
  45.   if xsamp = 0 then
  46.     acc
  47.   else
  48.     let pix =
  49.       [(NativePtr.get p 0,
  50.         NativePtr.get p 1,
  51.         NativePtr.get p 2)]
  52.     let p = NativePtr.add p 3 // We do *not* mutate here
  53.     getChunkRowPixels p (xsamp-1) (pix @ acc)
  54. // Get a chunk of pixels to average from multiple rows
  55. // We use a recursive function with an accumulator argument
  56. // to allow tail call optimization
  57. let rec getChunkPixels p stride xsamp ysamp acc =
  58.   if ysamp = 0 then
  59.     acc
  60.   else
  61.     let pix = getChunkRowPixels p xsamp []
  62.     let p = NativePtr.add p stride  // We do *not* mutate here
  63.     getChunkPixels p stride xsamp (ysamp-1) (pix @ acc)
  64. // Get the various chunks of pixels to average across
  65. // a complete bitmap image
  66. let pixelizeBitmap (image:System.Drawing.Bitmap) xsize ysize =
  67.   // Create a 1-dimensional array of pixel lists (one list,
  68.   // which then needs averaging, per final pixel)
  69.   let (arr : List[]) =
  70.     Array.create (xsize * ysize) []
  71.   // Lock the entire memory block related to our image
  72.   let bd =
  73.     image.LockBits
  74.       (System.Drawing.Rectangle
  75.         (0, 0, image.Width ,image.Height),
  76.       ImageLockMode.ReadOnly, image.PixelFormat)
  77.   // Establish the number of pixels to sample per chunk
  78.   // in each of the x and y directions
  79.   let xsamp = image.Width / xsize
  80.   let ysamp = image.Height / ysize
  81.   // We have a mutable pointer to step through the image
  82.   let mutable (p:nativeptr[b]) =
  83.     NativePtr.of_nativeint (bd.Scan0)
  84.   // Loop through the various chunks
  85.   for i = 0 to ysize - 1 do
  86.     // We take a copy of the current value of p, as we
  87.     // don't want to mutate p while extracting the pixels
  88.     // within a row
  89.     let mutable xp = p
  90.     for j = 0 to xsize - 1 do
  91.       // Get the square chunk of pixels starting at
  92.       // this x,y position
  93.       let chk =
  94.         getChunkPixels xp bd.Stride xsamp ysamp []
  95.       // Add it into our array
  96.       let idx = getIndexFromXY ysize j (ysize-1-i)
  97.       arr.[idx]  ids.[i] |]
  98. // Declare our command
  99. []
  100. let pixelize() =
  101.   // Let's get the usual helpful AutoCAD objects
  102.   let doc =
  103.     Application.DocumentManager.MdiActiveDocument
  104.   let ed = doc.Editor
  105.   let db = doc.Database
  106.   // Prompt the user for the file and the width of the image
  107.   let pofo =
  108.     new PromptOpenFileOptions
  109.       ("Select an image to import and pixelize")
  110.   pofo.Filter
  111.         pfnr.StringResult
  112.     | _ ->
  113.         ""
  114.   if System.IO.File.Exists(file) then
  115.     let img = System.Drawing.Image.FromFile(file)
  116.     let pio =
  117.       new PromptIntegerOptions
  118.         ("\nEnter number of horizontal pixels: ")
  119.     pio.AllowNone
  120.             img.Width
  121.         | PromptStatus.OK ->
  122.             pir.Value
  123.         | _ -> -1
  124.     if xsize > 0 then
  125.       // Calculate the vertical size from the horizontal
  126.       let ysize = img.Height * xsize / img.Width
  127.       if ysize > 0 then
  128.         // We'll time the command, so we can check the
  129.         // sync vs. async efficiency
  130.         let starttime = System.DateTime.Now
  131.         // "use" has the same effect as "using" in C#
  132.         use tr =
  133.           db.TransactionManager.StartTransaction()
  134.         // Get appropriately-typed BlockTable and BTRs
  135.         let bt =
  136.           tr.GetObject
  137.             (db.BlockTableId,OpenMode.ForRead)
  138.           :?> BlockTable
  139.         let ms =
  140.           tr.GetObject
  141.             (bt.[BlockTableRecord.ModelSpace],
  142.             OpenMode.ForWrite)
  143.           :?> BlockTableRecord
  144.         // Function to create a filled circle (hatch) at a
  145.         // specific location
  146.         // Note the valid use of tr and ms, as they are in scope
  147.         let createCircle pt rad =
  148.           let hat = new Hatch()
  149.           hat.SetDatabaseDefaults()
  150.           hat.SetHatchPattern
  151.             (HatchPatternType.PreDefined,
  152.             "SOLID")
  153.           let id = ms.AppendEntity(hat)
  154.           tr.AddNewlyCreatedDBObject(hat, true)
  155.           // Now we create the loop, which we make db-resident
  156.           // (appending a transient loop caused problems, so
  157.           // we're going to use the circle and then erase it)
  158.           let cir = new Circle()
  159.           cir.Radius  ignore
  160.           hat.AppendLoop(HatchLoopTypes.Default, loops)
  161.           hat.EvaluateHatch(true)
  162.           // Now we erase the loop
  163.           cir.Erase()
  164.           id
  165.         // Function to create our grid of circles
  166.         let createGrid xsize ysize rad offset =
  167.           let ids = new ObjectIdCollection()
  168.           for i = 0 to xsize - 1 do
  169.             for j = 0 to ysize - 1 do
  170.               let pt =
  171.                 new Point3d
  172.                   (offset * (Int32.to_float i),
  173.                    offset * (Int32.to_float j),
  174.                    0.0)
  175.               let id = createCircle pt rad
  176.               ids.Add(id) |> ignore
  177.           ids
  178.         // Function to change the colour of an entity
  179.         let changeColour (id : ObjectId) (col : Color) =
  180.           if id.IsValid then
  181.             let ent =
  182.               tr.GetObject(id, OpenMode.ForWrite) :?> Entity
  183.             ent.Color  System.Drawing.Bitmap
  184.         let arr = pixelizeBitmap bmp xsize ysize
  185.         // Loop through the pixel list and average them out
  186.         // (which could be parallelized), using the results
  187.         // to change the colour of the circles in our grid
  188.         Array.map getAverageColour arr |>
  189.           Array.iter2 changeColour (getIdArray ids)
  190.         // Commit the transaction
  191.         tr.Commit()
  192.         // Check how long it took
  193.         let elapsed =
  194.           System.DateTime.op_Subtraction
  195.             (System.DateTime.Now, starttime)
  196.         ed.WriteMessage
  197.           ("\nElapsed time: " + elapsed.ToString())
To change this to run the colour averaging asynchronously (in parallel, if you have the cores) is really simple. We replace one line of code "Array.map getAverageColour arr" with the following:
Async.Run
  (Async.Parallel
    [ for a in arr ->
        async { return getAverageColour a }])
This essentially performs a parallel array map (albeit a somewhat naive one), returning basically the same results as the previous line - just hopefully a little more quickly. In case you want to build the two files into one project to test them side-by-side, here they are, the synchronous and asynchronous versions, with the changes needed to allow them to live in and execute from the same assembly.
Here's one more image that's been processed by the PIX command:

ecm321wcx2k.png

ecm321wcx2k.png


In case you're interested, the original image can be found here.

y3ytar1ydrz.jpg

y3ytar1ydrz.jpg
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

  • 微信公众平台

  • 扫描访问手机版

  • 点击图片下载手机App

QQ|关于我们|小黑屋|乐筑天下 繁体中文

GMT+8, 2025-6-28 14:50 , Processed in 0.258101 second(s), 71 queries .

© 2020-2025 乐筑天下

联系客服 关注微信 帮助中心 下载APP 返回顶部 返回列表