digitalmars.D - std.concurrency and efficient returns
- Jonathan M Davis (40/40) Aug 01 2010 Okay. From what I can tell, it seems to be a recurring pattern with thre...
- Robert Jacques (20/90) Aug 01 2010 Hi Jonathan,
- Jonathan M Davis (13/31) Aug 01 2010 I totally agree that for the most part, the message passing as-is is a v...
- Jonathan M Davis (3/3) Aug 01 2010 FWIW, I posted an enhancement request on the subject:
- Robert Jacques (9/41) Aug 01 2010 Oh, sorry I missed that point. That has been seriously discussed before ...
- dsimcha (49/55) Aug 01 2010 I think the bottom line is that D's threading model is designed to put s...
- awishformore (14/69) Aug 01 2010 I completely agree with everything you said and I really dislike how D2
- awishformore (6/130) Aug 01 2010 P.S.: I find this to be especially true when taking into account the
- Pelle (3/137) Aug 01 2010 import core.thread;
- Robert Jacques (2/5) Aug 01 2010 Or use shared classes; you can pass those around too.
- dsimcha (15/55) Aug 02 2010 BTW, the other thing we need to make message passing viable even when sm...
- Sean Kelly (4/18) Aug 02 2010 Same here.
- Sean Kelly (2/16) Aug 02 2010 I wouldn't worry--the modules in core will always provide the low-level ...
- dsimcha (21/22) Aug 03 2010 that you're looking for. As for message passing, I think it should be t...
- Sean Kelly (2/15) Aug 03 2010 Ideally, the parallelism should be both invisible to the user and automa...
- dsimcha (9/24) Aug 03 2010 safe. That isn't a realistic goal though, so a balance must be found be...
- Robert Jacques (19/46) Aug 03 2010 My experience with data-parallel programming leads me to believe that a ...
- bearophile (4/10) Aug 04 2010 D has to learn a large number of things/tricks from Chapel. I have writt...
- dsimcha (24/33) Aug 04 2010 Given that Bartosz is a type system guru I can see where he's coming fro...
Okay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability. 1. _All_ of the arguments passed to spawn must be immutable. It's not that hard to be in a situation where you need to pass it arguments that the parent thread will never use, and it's highly probable that that data will have to be copied to make it immutable so that it can be passed. The result is that you're forced to make pointless copies. If you're passing a lot of data, that could be expensive. 2. _All_ of the arguments returned via send must be immutable. In the scenario that I'm describing here, the thread is going away after sending the message, so there's no way that it's going to do anything with the data, and having to copy it to make it immutable (as will likely have to be done) can be highly inefficient. Is there a better way to do this? Or if not, can one be created? It seems to me that it would be highly desirable to be able to pass mutable reference types between threads where the thread doing the receiving takes control of the object/array being passed. Due to D's threading model, a copy may still have to be done behind the scenes, but if you could pass mutable data across while passing ownership, you could have at most 1 copy rather than the 2 - 3 copies that would have to be taking place when you have a mutable obect that you're trying to send across threads (so, one copy to make it immutable, possibly a copy from one thread local storage to another of the immutable data (though I'd hope that that wouldn't require a copy), and one copy on the other end to get mutable data from the immutable data). As it stands, it seems painfully inefficient to me when you're passing anything other than small amounts of data across. Also, this recurring pattern that I'm seeing makes me wonder if it would be advantageous to have an addititon to std.concurrency where you spawned a thread which returned a value when it was done (rather than having to use a send with a void function), and the parent thread used a receive call of some kind to get the return value. Ideally, you could spawn a series of threads which were paired with the variables that their return values would be assigned to, and you could do it all as one function call. Overall, I really like D's threading model, but it seems to me that it could be streamlined a bit. - Jonathan M Davis
Aug 01 2010
On Sun, 01 Aug 2010 06:24:18 -0400, Jonathan M Davis <jmdavisprog gmail.com> wrote:Okay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability. 1. _All_ of the arguments passed to spawn must be immutable. It's not that hard to be in a situation where you need to pass it arguments that the parent thread will never use, and it's highly probable that that data will have to be copied to make it immutable so that it can be passed. The result is that you're forced to make pointless copies. If you're passing a lot of data, that could be expensive. 2. _All_ of the arguments returned via send must be immutable. In the scenario that I'm describing here, the thread is going away after sending the message, so there's no way that it's going to do anything with the data, and having to copy it to make it immutable (as will likely have to be done) can be highly inefficient. Is there a better way to do this? Or if not, can one be created? It seems to me that it would be highly desirable to be able to pass mutable reference types between threads where the thread doing the receiving takes control of the object/array being passed. Due to D's threading model, a copy may still have to be done behind the scenes, but if you could pass mutable data across while passing ownership, you could have at most 1 copy rather than the 2 - 3 copies that would have to be taking place when you have a mutable obect that you're trying to send across threads (so, one copy to make it immutable, possibly a copy from one thread local storage to another of the immutable data (though I'd hope that that wouldn't require a copy), and one copy on the other end to get mutable data from the immutable data). As it stands, it seems painfully inefficient to me when you're passing anything other than small amounts of data across. Also, this recurring pattern that I'm seeing makes me wonder if it would be advantageous to have an addititon to std.concurrency where you spawned a thread which returned a value when it was done (rather than having to use a send with a void function), and the parent thread used a receive call of some kind to get the return value. Ideally, you could spawn a series of threads which were paired with the variables that their return values would be assigned to, and you could do it all as one function call. Overall, I really like D's threading model, but it seems to me that it could be streamlined a bit. - Jonathan M DavisHi Jonathan, It sounds like what you really want is a task-based parallel programming library, as opposed to concurrent thread. I'd recommend Dave Simcha's parallelFuture library if you want to play around with this in D (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFutur /parallelFuture.d). However, parallelFuture is currently unsafe - you need to make sure that logically speaking that data the task is being passed is immutable. Shared/const/immutable delegates have been brought up before as a way to formalize the implicit assumptions of libraries like parallelFuture, but nothing has come of it yet. As for std.concurrency, immutability is definitely the correct way to go, even if it means extra copying: for most jobs the processing should greatly out way the cost of copying and thread initialization (though under the hood thread pools should help with the latter). A large amount of experience dictates that shared mutable data, let alone unprotected mutable data, is a bug waiting to happen. On a more practical note, if you relaxing either 1) or 2) can cause major problems with certain modern GCs, so at a minimum casts should be involved.
Aug 01 2010
On Sunday 01 August 2010 08:55:54 Robert Jacques wrote:Hi Jonathan, It sounds like what you really want is a task-based parallel programming library, as opposed to concurrent thread. I'd recommend Dave Simcha's parallelFuture library if you want to play around with this in D (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFuture/para llelFuture.d). However, parallelFuture is currently unsafe - you need to make sure that logically speaking that data the task is being passed is immutable. Shared/const/immutable delegates have been brought up before as a way to formalize the implicit assumptions of libraries like parallelFuture, but nothing has come of it yet. As for std.concurrency, immutability is definitely the correct way to go, even if it means extra copying: for most jobs the processing should greatly out way the cost of copying and thread initialization (though under the hood thread pools should help with the latter). A large amount of experience dictates that shared mutable data, let alone unprotected mutable data, is a bug waiting to happen. On a more practical note, if you relaxing either 1) or 2) can cause major problems with certain modern GCs, so at a minimum casts should be involved.I totally agree that for the most part, the message passing as-is is a very good idea. It's just that there are cases where it would be desirable to actually hand over data, so that the thread receiving the data owns it, and it doesn't exist anymore in the sending thread. I'm not sure that that's possible in the general case as nice as it would be. However, in my specific case - effectively returning data upon thread termination - I should think that it would be totally possible since the sending thread is terminating. That would require some extra functions in std.concurrency however rather than using receive() and send() as we have them. In any case, I'll have to look at Dave Simcha's parallelism library. Thanks for the info. - Jonathan M Davis
Aug 01 2010
FWIW, I posted an enhancement request on the subject: http://d.puremagic.com/issues/show_bug.cgi?id=4566 - Jonthan M Davis
Aug 01 2010
On Sun, 01 Aug 2010 19:22:10 -0400, Jonathan M Davis <jmdavisprog gmail.com> wrote:On Sunday 01 August 2010 08:55:54 Robert Jacques wrote:Oh, sorry I missed that point. That has been seriously discussed before under the moniker of a 'unique'/'mobile' type. You might want to look up the dmd-concurrency mailing list or Bartoz's old blogs bartoszmilewski.wordpress.com. If I recall correctly, there was some plans to support the library unique struct in std.concurrency. However, Walter found that trying to fit it into the type system as a whole was too complex, so the concept is being left to the simpler library solution.Hi Jonathan, It sounds like what you really want is a task-based parallel programming library, as opposed to concurrent thread. I'd recommend Dave Simcha's parallelFuture library if you want to play around with this in D (http://www.dsource.org/projects/scrapple/browser/trunk/parallelFuture/para llelFuture.d). However, parallelFuture is currently unsafe - you need to make sure that logically speaking that data the task is being passed is immutable. Shared/const/immutable delegates have been brought up before as a way to formalize the implicit assumptions of libraries like parallelFuture, but nothing has come of it yet. As for std.concurrency, immutability is definitely the correct way to go, even if it means extra copying: for most jobs the processing should greatly out way the cost of copying and thread initialization (though under the hood thread pools should help with the latter). A large amount of experience dictates that shared mutable data, let alone unprotected mutable data, is a bug waiting to happen. On a more practical note, if you relaxing either 1) or 2) can cause major problems with certain modern GCs, so at a minimum casts should be involved.I totally agree that for the most part, the message passing as-is is a very good idea. It's just that there are cases where it would be desirable to actually hand over data, so that the thread receiving the data owns it, and it doesn't exist anymore in the sending thread. I'm not sure that that's possible in the general case as nice as it would be.
Aug 01 2010
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s articleOkay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability.I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
Aug 01 2010
On 01/08/2010 19:17, dsimcha wrote:== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s articleI completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /MaxOkay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability.I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
Aug 01 2010
On 01/08/2010 21:25, awishformore wrote:On 01/08/2010 19:17, dsimcha wrote:P.S.: I find this to be especially true when taking into account the pragmatic approach under which D is supposed to be designed. D2 sounds a lot more idealistic than pragmatic, especially when it comes to concurrency, and I find that to be a very worrisome development. /Max== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s articleI completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /MaxOkay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability.I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
Aug 01 2010
On 08/01/2010 09:28 PM, awishformore wrote:On 01/08/2010 21:25, awishformore wrote:import core.thread; You don't have to use the message passing interface if you don't want to.On 01/08/2010 19:17, dsimcha wrote:P.S.: I find this to be especially true when taking into account the pragmatic approach under which D is supposed to be designed. D2 sounds a lot more idealistic than pragmatic, especially when it comes to concurrency, and I find that to be a very worrisome development. /Max== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s articleI completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants. /MaxOkay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability.I think the bottom line is that D's threading model is designed to put safety and simplicity over performance and flexibility. Given the amount of bugs that are apparently generated when using threading for concurrency in large-scale software written by hordes of programmers, this may be a reasonable tradeoff. Within the message-passing model, one thing that would help a lot is a Unique type that can be implicitly and destructively converted to immutable or shared. In D as it stands right now, immutable is basically useless in all but the simplest cases because it's just too hard to build complex immutable data structures, especially if you want to avoid unnecessary copying or having to rely on casts and manually checked assumptions in at least small areas of the program. In theory, immutable solves tons of problems, but in practice it solves very few. While I don't understand shared that well, I guess a Unique type would help in creating shared data, too. There are two reasons for using multithreading: Parallelism (using multiple cores to increase throughput) and concurrency (making things appear to be happening simultaneously to decrease latency; this makes sense even on a single-core machine). One may come as a side effect of the other, but usually only one is the goal. It sounds like you're looking for parallelism. When using threading for parallelism as opposed to concurrency, this tradeoff of simplicity and safety in exchange for flexibility and performance doesn't work so well because: 1. When using threading for parallelism instead of concurrency, it's reasonable to do some unsafe stuff to get better performance, since performance is the whole point anyhow. 2. Unlike the concurrency case, the parallelism case usually occurs only in small hotspots of a program, or in small scientific computing programs. In these cases it's not that hard for the programmer to manually track what's shared, etc. 3. In my experience at least, parallelism often requires finer grained communication between threads than concurrency. For example, an OS timeslice is about 15 milliseconds, meaning that on single core machines threads being used for concurrency simply can't communicate more often than that. I've written useful parallel code that scaled to at least 4 cores and required communication between threads several times per millisecond. It could have been written more efficiently w.r.t. communication between threads, but it would have required a lot more memory allocations and been less efficient in other respects. While I completely agree that message passing should be D's **flagship** threading model because it's been proven to work well in a lot of cases, I'm not sure if it should be the **only** one well-supported out of the box because it's just too inflexible when you want pull-out-all-stops parallelism. As Robert Jacques mentioned, I've been working on a parallelism library. The code is at: http://dsource.org/projects/scrapple/browser/trunk/parallelFuture/parallelFuture.d The docs are at: http://cis.jhu.edu/~dsimcha/parallelFuture.html I've been thinking lately about how to integrate this into the new threading model, as it's currently completely unsafe, doesn't use shared at all, and was written before the new threading model was implemented. (core.thread still takes an unshared delegate). I think before we can solve the problems you've brought up, we need to clarify how non-message passing based multithreading (i.e. using shared) is going to work in D, as right now it is completely unclear at least to me.
Aug 01 2010
On Sun, 01 Aug 2010 16:02:43 -0400, Pelle <pelle.mansson gmail.com> wrote:On 08/01/2010 09:28 PM, awishformore wrote: import core.thread; You don't have to use the message passing interface if you don't want to.Or use shared classes; you can pass those around too.
Aug 01 2010
== Quote from Jonathan M Davis (jmdavisprog gmail.com)'s articleOkay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability. 1. _All_ of the arguments passed to spawn must be immutable. It's not that hard to be in a situation where you need to pass it arguments that the parent thread will never use, and it's highly probable that that data will have to be copied to make it immutable so that it can be passed. The result is that you're forced to make pointless copies. If you're passing a lot of data, that could be expensive. 2. _All_ of the arguments returned via send must be immutable. In the scenario that I'm describing here, the thread is going away after sending the message, so there's no way that it's going to do anything with the data, and having to copy it to make it immutable (as will likely have to be done) can be highly inefficient. Is there a better way to do this? Or if not, can one be created? It seems to me that it would be highly desirable to be able to pass mutable reference types between threads where the thread doing the receiving takes control of the object/array being passed. Due to D's threading model, a copy may still have to be done behind the scenes, but if you could pass mutable data across while passing ownership, you could have at most 1 copy rather than the 2 - 3 copies that would have to be taking place when you have a mutable obect that you're trying to send across threads (so, one copy to make it immutable, possibly a copy from one thread local storage to another of the immutable data (though I'd hope that that wouldn't require a copy), and one copy on the other end to get mutable data from the immutable data). As it stands, it seems painfully inefficient to me when you're passing anything other than small amounts of data across. Also, this recurring pattern that I'm seeing makes me wonder if it would be advantageous to have an addititon to std.concurrency where you spawned a thread which returned a value when it was done (rather than having to use a send with a void function), and the parent thread used a receive call of some kind to get the return value. Ideally, you could spawn a series of threads which were paired with the variables that their return values would be assigned to, and you could do it all as one function call. Overall, I really like D's threading model, but it seems to me that it could be streamlined a bit. - Jonathan M DavisBTW, the other thing we need to make message passing viable even when small efficiencies don't count is an easy way of sending a deep clone of an arbitrarily complex object graph (clone would, I guess, return a Unique type) as a message to another thread. Of course there are some obvious limitations on what can be cloned. For example, raw pointers to data structures containing more pointer indirection couldn't be automagically deep cloned because you wouldn't know where the boundary between valid data and invalid data is. However, if I could just do something like: // myObj is unshared and is an arbitrarily complex object graph // that consists entirely of SafeD-style objects, not raw pointers. auto myObj = buildReallyComplicatedObject(); auto tid = spawn(&myFun, myObj.clone()); then I'd consider D's implementation of message passing viable for non-trivial use cases, as long as simplicity and safety matter more than performance and flexibility.
Aug 02 2010
Jonathan M Davis Wrote:Okay. From what I can tell, it seems to be a recurring pattern with threads that it's useful to spawn a thread, have it do some work, and then have it return the result and terminate. The appropriate way to do that seems to spawn the thread with the data that needs to be passed and then using send to send what would normally be the return value before the function (and therefore the spawned thread) terminates. I see 2 problems with this, both stemming from immutability. 1. _All_ of the arguments passed to spawn must be immutable.They must be shared. Immutable data is just implicitly shared.2. _All_ of the arguments returned via send must be immutable.Same here.In the scenario that I'm describing here, the thread is going away after sending the message, so there's no way that it's going to do anything with the data, and having to copy it to make it immutable (as will likely have to be done) can be highly inefficient.It sounds like what you really want is a thread pool. Maybe passing a shared delegate would work? It would be really nice to get unique sorted out though. Passing unique data via tid.send() should work too.
Aug 02 2010
awishformore Wrote:I completely agree with everything you said and I really dislike how D2 currently seems to virtually impose an application architecture based on the message passing model if you don't want to circumvent and thus break the entire type system. While I do agree that message passing makes a lot of sense as the default choice, there also has to be well thought-out and extensive support for the shared memory model if D2 is really focusing on the concurrency issue as much as it claims. Personally, I've found hybrid architectures where both models are combined as needed to be the most flexible and best performing approach and there is no way a language touted to be a systems language should impose one model over the other and stop the programmer from doing things the way he wants.I wouldn't worry--the modules in core will always provide the low-level control that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well.
Aug 02 2010
== Quote from Sean Kelly (sean invisibleduck.org)'s articleI wouldn't worry--the modules in core will always provide the low-level controlthat you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading? My personal opinion (which may change if I understand shared better) is that "here be dragons" is the way to go. Futures/tasks/parallel foreach are meant for parallelizing small hotspots, where the programmer generally knows implicitly what assumptions are being made, and generally revolve completely around shared memory and, where necessary, explicit locking. Forcing things to be cast to shared left, right and center would simply be an annoying piece of syntactic salt that doesn't actually solve any problems in these cases. (It would also completely destroy the nice, clean interface of the module I've created for task/future/parallel foreach.) I think Phobos's std.parallelism should just come with a big "here be dragons" sign and that's about it as far as safety/use of shared/etc.
Aug 03 2010
dsimcha Wrote:== Quote from Sean Kelly (sean invisibleduck.org)'s articleIdeally, the parallelism should be both invisible to the user and automatically safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it.I wouldn't worry--the modules in core will always provide the low-level controlthat you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Aug 03 2010
== Quote from Sean Kelly (sean invisibleduck.org)'s articledsimcha Wrote:safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it. My other major concern is that if too much convenience (in simple cases where the programmer does know what he/she is doing even if the guarantees aren't statically checkable) is traded for safety (i.e. by requiring stuff to be cast explicitly to shared in lots of places), the API will become almost unusable.== Quote from Sean Kelly (sean invisibleduck.org)'s articleIdeally, the parallelism should be both invisible to the user and automaticallyI wouldn't worry--the modules in core will always provide the low-level controlthat you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Aug 03 2010
On Tue, 03 Aug 2010 16:05:40 -0400, Sean Kelly <sean invisibleduck.org> wrote:dsimcha Wrote:My experience with data-parallel programming leads me to believe that a large number of use cases could be covered by extending the D's set of function/member modifiers (i.e. const/shared/immutable/pure) to cover delegates. This would allow, for instance, a parallel foreach function to take a const delegate or a future function to take a shared delegate and thereby provide both safety and performance. Bartosz recently bloged about task driven parallelism in three "High Productivity Computing Systems" languages ( Chapel, X10, Fortress ) and criticized all three regarding taking the "here be dragons" approach. Even experts wake up the dragons on a semi-regular basis and debugging them back into slumber is both hard and time consuming; it's typically to see a 10x cost in development time on massively parallel architectures. For mere mortals to be productive, those dragons need to be slain. And I think the D has a decent chance to be the first(?) non-functional language to slay them. But I think this should be a goal of D3, and that a "here be dragons" library for D2 would be extremely useful: because if we're going to slay the dragons, we need to know as about many caves and dragons as possible.== Quote from Sean Kelly (sean invisibleduck.org)'s articleIdeally, the parallelism should be both invisible to the user and automatically safe. That isn't a realistic goal though, so a balance must be found between performance and safety. I'd venture to say the "here be dragons" approach is correct for the most part though. If too much performance is traded away for safety, no one will use it.I wouldn't worry--the modules in core will always provide thelow-level control that you're looking for. As for message passing, I think it should be the default choice but not the only choice. Things like futures are a reasonable addition to Phobos as well. How explicit do we want to make inter-thread sharing when using a futures/tasks/parallel foreach library? This question may be a result of the fact that I (still) don't understand what shared is supposed to do, but do we want to force all data to be shared/cast to shared even under a futures/tasks/parallel foreach model, or do we want to bypass the shared system entirely and simply stick to old-fashioned "here be dragons" when doing future/task/parallel foreach-based multithreading?
Aug 03 2010
Robert Jacques:Bartosz recently bloged about task driven parallelism in three "High Productivity Computing Systems" languages ( Chapel, X10, Fortress ) and criticized all three regarding taking the "here be dragons" approach. Even experts wake up the dragons on a semi-regular basis and debugging them back into slumber is both hard and time consuming;D has to learn a large number of things/tricks from Chapel. I have written two posts to show some of them. Bye, bearophile
Aug 04 2010
== Quote from Robert Jacques (sandford jhu.edu)'s articleMy experience with data-parallel programming leads me to believe that a large number of use cases could be covered by extending the D's set of function/member modifiers (i.e. const/shared/immutable/pure) to cover delegates. This would allow, for instance, a parallel foreach function to take a const delegate or a future function to take a shared delegate and thereby provide both safety and performance. Bartosz recently bloged about task driven parallelism in three "High Productivity Computing Systems" languages ( Chapel, X10, Fortress ) and criticized all three regarding taking the "here be dragons" approach.Given that Bartosz is a type system guru I can see where he's coming from. However, ironically we're talking about making such a library usable for mere mortals and I consider myself a mere mortal when it comes the complexities of type systems, in that I find Bartosz's posts extremely theoretical and difficult to follow. I actually find it easier to visualize how work can be interleaved between threads. Since shared is relatively new and (I think) not fully implemented, immutable is a good example of why not everything can be easily expressed in the type system. Immutable data has some wonderful theoretical properties, but creating immutable data in D without either sacrificing a significant amount of efficiency (via copying) or relying on unchecked casts, is close to impossible. Yes, we could have made unique a full-fledged type constructor, but that would have added another level of complexity to the language when const/immutable already seems complex to a lot of people. The result of this is that much data that I share across threads is logically immutable, but I've given up long ago on making most cases of this statically checkable. Also, with regard to the 10x development cost, I suspect a lot of that has to do with getting the code to scale, too. Code already becomes substantially harder to write, for example, when you can't freely heap allocate whenever you want (because you'd bottleneck on malloc and GC). Since we're talking about shared memory architectures here, I'll assume the cases you're referring to don't have thread-local heaps and that at least some synchronization points are required for memory management.
Aug 04 2010