[Concurrency] A slightly different perspective

This doesn't work for priority tracking purposes, and is bad for locking domains too.

What you really want here is:

let groupLike : Dispatch.SomethingThatLooksLikeAGroupButDoesTracking()

myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
groupLike.notify(myImageProcessingQueue()) {
    // decodeImage
}

The two main differences with what you explained is:

1) `groupLike` would definitely be the underlying thing that tracks dependencies which is required for a higher level Future-like construct (which async/await doesn't have to solve, provided that it captures enough context for the sake of such a groupLike object).

`groupLike` would likely *NOT* be an object developers would manipulate directly but rather the underlying mechanism.

2) the loadWebResource is done from the same serial context, because networking is already (if your library is sane) using an asynchronous interface that is very rarely a CPU bound problem, so parallelizing it is not worth it because synchronization cost will dominate. To give hindsight, our WWDC talk at the beginning pitched this measured performance win in a real life scenario:

1.3x

faster after combining queue hierarchies

This is covered here:
Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer;
Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer; and onward

It happens that this 30%+ performance win that we discuss here happens to have actually be with how some subsystems were using our networking stack, by recombining code that was essentially doing what you wrote into what I just wrote above by using the same exclusion context for all networking.

If Swift async/await leads to people writing things equivalent to using the global queue the way you suggest, we failed from a system performance perspective.

-Pierre

···

On Sep 4, 2017, at 12:55 PM, Wallacy via swift-evolution <swift-evolution@swift.org> wrote:

Yes, maybe in this way... Or using dispatch_group..

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_notify(group,dispatch_get_global_queue(0, 0), ^ {
    // decodeImage ... etc...
});

Can be made using different strategies, the compiler will select the best fit for every case. Different runtimes, has different "best" strategies also. No need to use a intermediary type.

Em seg, 4 de set de 2017 às 14:53, Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> escreveu:

> Le 4 sept. 2017 à 10:01, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
>
> func processImageData1a() async ->
> Image {
> let dataResource = async loadWebResource("dataprofile.txt")
> let imageResource = async loadWebResource("imagedata.dat")
>
> // ... other stuff can go here to cover load latency...
>
> let imageTmp = await decodeImage(dataResource, imageResource) // compiler error if await is not present.
> let imageResult = await dewarpAndCleanupImage(imageTmp)
> return imageResult
> }
>
>
> If this (or something like that) is not implemented, people will create several versions to solve the same problem, so that later (Swift 6?) will be solved (because people want this), and we will live with several bad codes to maintain.

Just to be sure of what you are proposing, am I right to assume this would be compiled down to something like this?

func processImageData1a(completion: (Image) -> ()) {
  var dataResource: Resource? = nil
  var imageResource: Resource? = nil
  var finishedBody = false

  func continuation() {
    // only continue once everything is ready
    guard finishedBody else { return }
    guard dataResource = dataResource else { return }
    guard imageResource = imageResource else { return }

    // everything is ready now
    decodeImage(dataResource, imageResource) { imageTmp in
      dewarpAndCleanupImage(imageTmp) { imageResult in
        completion(imageResult)
      }
    }
  }

  loadWebResource("dataprofile.txt") { result in
    dataResource = result
    continuation()
  }
  loadWebResource("imagedata.dat") { result in
    imageResource = result
    continuation()
  }

  // ... other stuff can go here to cover load latency...

  finishedBody = true
  continuation()
}

This seems more lightweight than a future to me. I know I've used this pattern a few times. What I'm not sure about is how thrown errors would work. Surely you want error handling to work when loading resources from the web.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/&gt;

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Thanks for this Pierre!

I think one of the main reasons I am proposing this is that this stuff is so easy to get wrong. I know I have gotten it wrong before in subtle ways which were really difficult to debug. By allowing the programmer to declare their intent, but not the implementation, we hopefully allow the right thing to happen behind the scenes (even if the programmer doesn’t know what that right thing is). In some cases, that right thing™ might even be to just do things serially behind the scenes to avoid the overhead of synchronizing.

Thanks,
Jon

···

On Sep 4, 2017, at 1:48 PM, Pierre Habouzit via swift-evolution <swift-evolution@swift.org> wrote:

This doesn't work for priority tracking purposes, and is bad for locking domains too.

What you really want here is:

let groupLike : Dispatch.SomethingThatLooksLikeAGroupButDoesTracking()

myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
groupLike.notify(myImageProcessingQueue()) {
    // decodeImage
}

The two main differences with what you explained is:

1) `groupLike` would definitely be the underlying thing that tracks dependencies which is required for a higher level Future-like construct (which async/await doesn't have to solve, provided that it captures enough context for the sake of such a groupLike object).

`groupLike` would likely *NOT* be an object developers would manipulate directly but rather the underlying mechanism.

2) the loadWebResource is done from the same serial context, because networking is already (if your library is sane) using an asynchronous interface that is very rarely a CPU bound problem, so parallelizing it is not worth it because synchronization cost will dominate. To give hindsight, our WWDC talk at the beginning pitched this measured performance win in a real life scenario:

1.3x

faster after combining queue hierarchies

This is covered here:
Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer;
https://developer.apple.com/videos/play/wwdc2017/706/?time=1500 <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer; and onward

It happens that this 30%+ performance win that we discuss here happens to have actually be with how some subsystems were using our networking stack, by recombining code that was essentially doing what you wrote into what I just wrote above by using the same exclusion context for all networking.

If Swift async/await leads to people writing things equivalent to using the global queue the way you suggest, we failed from a system performance perspective.

-Pierre

On Sep 4, 2017, at 12:55 PM, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Yes, maybe in this way... Or using dispatch_group..

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_notify(group,dispatch_get_global_queue(0, 0), ^ {
    // decodeImage ... etc...
});

Can be made using different strategies, the compiler will select the best fit for every case. Different runtimes, has different "best" strategies also. No need to use a intermediary type.

Em seg, 4 de set de 2017 às 14:53, Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> escreveu:

> Le 4 sept. 2017 à 10:01, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
>
> func processImageData1a() async ->
> Image {
> let dataResource = async loadWebResource("dataprofile.txt")
> let imageResource = async loadWebResource("imagedata.dat")
>
> // ... other stuff can go here to cover load latency...
>
> let imageTmp = await decodeImage(dataResource, imageResource) // compiler error if await is not present.
> let imageResult = await dewarpAndCleanupImage(imageTmp)
> return imageResult
> }
>
>
> If this (or something like that) is not implemented, people will create several versions to solve the same problem, so that later (Swift 6?) will be solved (because people want this), and we will live with several bad codes to maintain.

Just to be sure of what you are proposing, am I right to assume this would be compiled down to something like this?

func processImageData1a(completion: (Image) -> ()) {
  var dataResource: Resource? = nil
  var imageResource: Resource? = nil
  var finishedBody = false

  func continuation() {
    // only continue once everything is ready
    guard finishedBody else { return }
    guard dataResource = dataResource else { return }
    guard imageResource = imageResource else { return }

    // everything is ready now
    decodeImage(dataResource, imageResource) { imageTmp in
      dewarpAndCleanupImage(imageTmp) { imageResult in
        completion(imageResult)
      }
    }
  }

  loadWebResource("dataprofile.txt") { result in
    dataResource = result
    continuation()
  }
  loadWebResource("imagedata.dat") { result in
    imageResource = result
    continuation()
  }

  // ... other stuff can go here to cover load latency...

  finishedBody = true
  continuation()
}

This seems more lightweight than a future to me. I know I've used this pattern a few times. What I'm not sure about is how thrown errors would work. Surely you want error handling to work when loading resources from the web.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/&gt;

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The first implementation I proposed before Wallacy suggested using dispatch_group_t does not involve any dispatching. It's possible that loadWebResource would dispatch in the background, but dispatching is not necessary either.

For instance, loadWebResource could just be a wrapper for CFNetwork or NSURLRequest that handles its callbacks on the current queue/runloop when network events occurs and then call its completion block once it's done. loadWebResource does not have to do any dispatching itself in this case, it's all handled by the frameworks.

And if a resource is already cached in memory, loadWebResource could call the completion block immediately with the data before exiting, nothing asynchronous needing to happen in that case.

The generated code I proposed for this would work very well for these cases because it does not dispatch the calls to loadWebResource itself. No dispatching actually occurs in the case the resource is already available in the cache and everything runs smoothly without a single context switch. loadWebResource would only resort to dispatch if/when necessary.

I'm just not sure how well that would work for priority tracking in this case since we are crossing function boundaries.

···

Le 4 sept. 2017 à 16:48, Pierre Habouzit <phabouzit@apple.com> a écrit :

This doesn't work for priority tracking purposes, and is bad for locking domains too.

What you really want here is:

let groupLike : Dispatch.SomethingThatLooksLikeAGroupButDoesTracking()

myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
groupLike.notify(myImageProcessingQueue()) {
    // decodeImage
}

The two main differences with what you explained is:

1) `groupLike` would definitely be the underlying thing that tracks dependencies which is required for a higher level Future-like construct (which async/await doesn't have to solve, provided that it captures enough context for the sake of such a groupLike object).

`groupLike` would likely *NOT* be an object developers would manipulate directly but rather the underlying mechanism.

2) the loadWebResource is done from the same serial context, because networking is already (if your library is sane) using an asynchronous interface that is very rarely a CPU bound problem, so parallelizing it is not worth it because synchronization cost will dominate. To give hindsight, our WWDC talk at the beginning pitched this measured performance win in a real life scenario:

1.3x

faster after combining queue hierarchies

This is covered here:
Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer;
https://developer.apple.com/videos/play/wwdc2017/706/?time=1500 <Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer; and onward

It happens that this 30%+ performance win that we discuss here happens to have actually be with how some subsystems were using our networking stack, by recombining code that was essentially doing what you wrote into what I just wrote above by using the same exclusion context for all networking.

If Swift async/await leads to people writing things equivalent to using the global queue the way you suggest, we failed from a system performance perspective.

-Pierre

On Sep 4, 2017, at 12:55 PM, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Yes, maybe in this way... Or using dispatch_group..

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource
});

dispatch_group_notify(group,dispatch_get_global_queue(0, 0), ^ {
    // decodeImage ... etc...
});

Can be made using different strategies, the compiler will select the best fit for every case. Different runtimes, has different "best" strategies also. No need to use a intermediary type.

Em seg, 4 de set de 2017 às 14:53, Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> escreveu:

> Le 4 sept. 2017 à 10:01, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
>
> func processImageData1a() async ->
> Image {
> let dataResource = async loadWebResource("dataprofile.txt")
> let imageResource = async loadWebResource("imagedata.dat")
>
> // ... other stuff can go here to cover load latency...
>
> let imageTmp = await decodeImage(dataResource, imageResource) // compiler error if await is not present.
> let imageResult = await dewarpAndCleanupImage(imageTmp)
> return imageResult
> }
>
>
> If this (or something like that) is not implemented, people will create several versions to solve the same problem, so that later (Swift 6?) will be solved (because people want this), and we will live with several bad codes to maintain.

Just to be sure of what you are proposing, am I right to assume this would be compiled down to something like this?

func processImageData1a(completion: (Image) -> ()) {
  var dataResource: Resource? = nil
  var imageResource: Resource? = nil
  var finishedBody = false

  func continuation() {
    // only continue once everything is ready
    guard finishedBody else { return }
    guard dataResource = dataResource else { return }
    guard imageResource = imageResource else { return }

    // everything is ready now
    decodeImage(dataResource, imageResource) { imageTmp in
      dewarpAndCleanupImage(imageTmp) { imageResult in
        completion(imageResult)
      }
    }
  }

  loadWebResource("dataprofile.txt") { result in
    dataResource = result
    continuation()
  }
  loadWebResource("imagedata.dat") { result in
    imageResource = result
    continuation()
  }

  // ... other stuff can go here to cover load latency...

  finishedBody = true
  continuation()
}

This seems more lightweight than a future to me. I know I've used this pattern a few times. What I'm not sure about is how thrown errors would work. Surely you want error handling to work when loading resources from the web.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/&gt;

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca

Thanks Pierre,

When I answered, I was using my smartphone, so I copied the first code
snippet from stackoverflow I found and remove some words. And in that case
I'm happy to be a flawed example, and yet, its was most voted on
stackoverflow. (the full exemple also does other mistakes).

As Jonathan said, this only proves that it is important to have some basic
primitive for parallelism. To avoid common mistakes. We dont need nothing
complex rigth now! As Chris sayed, we are only making the ground layer....
But need to be a good and userfull layer to avoid rework in the future.

(Also, i see the wwdc talk. Good one, god job)

···

Em seg, 4 de set de 2017 às 17:48, Pierre Habouzit <phabouzit@apple.com> escreveu:

This doesn't work for priority tracking purposes, and is bad for locking
domains too.

What you really want here is:

let groupLike : Dispatch.SomethingThatLooksLikeAGroupButDoesTracking()

myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
myNetworkingQueue().async(group: groupLike) {
    // loadWebResource
}
groupLike.notify(myImageProcessingQueue()) {
    // decodeImage
}

The two main differences with what you explained is:

1) `groupLike` would definitely be the underlying thing that tracks
dependencies which is required for a higher level Future-like construct
(which async/await doesn't have to solve, provided that it captures enough
context for the sake of such a groupLike object).

`groupLike` would likely *NOT* be an object developers would manipulate
directly but rather the underlying mechanism.

2) the loadWebResource is done from the same serial context, because
networking is already (if your library is sane) using an asynchronous
interface that is very rarely a CPU bound problem, so parallelizing it is
not worth it because synchronization cost will dominate. To give hindsight,
our WWDC talk at the beginning pitched this measured performance win in a
real life scenario:

1.3x

faster after combining queue hierarchies

This is covered here:

https://developer.apple.com/videos/play/wwdc2017/706/?time=138
Modernizing Grand Central Dispatch Usage - WWDC17 - Videos - Apple Developer and onward

It happens that this 30%+ performance win that we discuss here happens to
have actually be with how some subsystems were using our networking stack,
by recombining code that was essentially doing what you wrote into what I
just wrote above by using the same exclusion context for all networking.

If Swift async/await leads to people writing things equivalent to using
the global queue the way you suggest, we failed from a system performance
perspective.

-Pierre

On Sep 4, 2017, at 12:55 PM, Wallacy via swift-evolution < > swift-evolution@swift.org> wrote:

Yes, maybe in this way... Or using dispatch_group..

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource});

dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^ {
    // loadWebResource});

dispatch_group_notify(group,dispatch_get_global_queue(0, 0), ^ {
    // decodeImage ... etc...});

Can be made using different strategies, the compiler will select the best
fit for every case. Different runtimes, has different "best" strategies
also. No need to use a intermediary type.

Em seg, 4 de set de 2017 às 14:53, Michel Fortin <michel.fortin@michelf.ca> > escreveu:

> Le 4 sept. 2017 à 10:01, Wallacy via swift-evolution < >> swift-evolution@swift.org> a écrit :
>
> func processImageData1a() async ->
> Image {
> let dataResource = async loadWebResource("dataprofile.txt")
> let imageResource = async loadWebResource("imagedata.dat")
>
> // ... other stuff can go here to cover load latency...
>
> let imageTmp = await decodeImage(dataResource, imageResource) //
compiler error if await is not present.
> let imageResult = await dewarpAndCleanupImage(imageTmp)
> return imageResult
> }
>
>
> If this (or something like that) is not implemented, people will create
several versions to solve the same problem, so that later (Swift 6?) will
be solved (because people want this), and we will live with several bad
codes to maintain.

Just to be sure of what you are proposing, am I right to assume this
would be compiled down to something like this?

func processImageData1a(completion: (Image) -> ()) {
  var dataResource: Resource? = nil
  var imageResource: Resource? = nil
  var finishedBody = false

  func continuation() {
    // only continue once everything is ready
    guard finishedBody else { return }
    guard dataResource = dataResource else { return }
    guard imageResource = imageResource else { return }

    // everything is ready now
    decodeImage(dataResource, imageResource) { imageTmp in
      dewarpAndCleanupImage(imageTmp) { imageResult in
        completion(imageResult)
      }
    }
  }

  loadWebResource("dataprofile.txt") { result in
    dataResource = result
    continuation()
  }
  loadWebResource("imagedata.dat") { result in
    imageResource = result
    continuation()
  }

  // ... other stuff can go here to cover load latency...

  finishedBody = true
  continuation()
}

This seems more lightweight than a future to me. I know I've used this
pattern a few times. What I'm not sure about is how thrown errors would
work. Surely you want error handling to work when loading resources from
the web.

--
Michel Fortin
https://michelf.ca

_______________________________________________

swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution