Earlier quoted context omitted.
That is a good point. If text rendering is slow, why are you not doing it in parallel? This is what 9rx called out earlier.
Some hypothetical example numbers: if software-rendering text takes 0.1 milliseconds, and I have a handful of text strings to render, I may not care that rendering the strings takes a millisecond or two. But that 0.1 millisecond to render a string is an eternity compared to the time it takes to allocate some memory, which might be on the order of single digit microseconds. Saving a microsecond from a process which ta…
Defer available in gcc and clang
221–230 of 262 posts
Re: Defer available in gcc and clang
#222Earlier quoted context omitted.
For concurrent processing you'd probably do something like splitting the file names into several batches and process those batches sequentially in each goroutine, so it's very much possible that you'd have an exact same loop for the concurrent scenario. P.S. If you have enough files you don't want to try to open them all at once — Go will start creating more and more threads to handle the "blocked" syscalls (open(2)…
You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why. If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decision…
for filename := range workQueue {
fp, err := os.Open(filename)
if err != nil { ... }
defer fp.Close()
// do work
}
Which would have the same exact problem :)Re: Defer available in gcc and clang
#223Earlier quoted context omitted.
You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why. If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decision…
Using a "work queue", i.e. a channel would still have a for loop like for filename := range workQueue { fp, err := os.Open(filename) if err != nil { ... } defer fp.Close() // do work } Which would have the same exact problem :)
for _, filename := range files {
queue
or more realistically, var group errgroup.Group
group.SetLimit(10)
for _, filename := range files {
group.Go(func() error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", filename, err)
}
defer f.Close()
// ...
return nil
})
}
if err := group.Wait(); err != nil {
return fmt.Errorf("failed to process files: %w", err)
}
Perhaps you can elaborate?I did read your code, but it is not clear where the worker queue is. It looks like it ranges over (presumably) a channel of filenames, which is not meaningfully different than ranging over a slice of filenames. That is the original, non-concurrent solution, more or less.
Re: Defer available in gcc and clang
#224Earlier quoted context omitted.
Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks. using (var resource = acquire()) { } // implicit resource.Dispose(); While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like…
That is a different approach, but I don't think you've demonstrated why it's better. Seems like that approach forces you to introduce a new scope for every resource, which might otherwise be unnecessary: using (var resource1 = acquire() { using (var resource2 = acquire()) { using (var resource3 = acquire()) { // use resources here.. } } } Compared to: var resource1 = acquire(); defer { release(resource1); } var resou…
using (auto res1 = acquire1(); free(res1))
using (auto res2 = acquire2(); free(res2))
using (auto res3 = acquire3(); free(res3))
{
// use resources here
}
// free(res3); free(res2); free(res1); called in that order.
The argument for this approach is it is structural. `defer` statements are not structural control flow: They're `goto` or `comefrom` in disguise.---
Even if we didn't want to introduce new scope, we could have something like F#'s `use`[1], which makes the resource available until the end of the scope it was introduced.
use auto res1 = acquire1() defer { free(res1); };
use auto res2 = acquire2() defer { free(res2); };
use auto res3 = acquire3() defer { free(res3); };
// use resources here
In either case (using or use-defer), the acquisition and release are coupled together in the code. With `defer` statements they're scattered as separate statements. The main argument for `defer` is to keep the acquisition and release of resources together in code, but defer statements fail at doing that.[1]:https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
Re: Defer available in gcc and clang
#225The article is a bit dense, but what it's announcing is effectively golang's `defer` (with extra braces) or a limited form of C++'s RAII (with much less boilerplate). Both RAII and `defer` have proven to be highly useful in real-world code. This seems like a good addition to the C language that I hope makes it into the standard.
The extra braces appear to be optional according to the examples in https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf (see pages 13-14)
Re: Defer available in gcc and clang
#226Earlier quoted context omitted.
What's the use-case for block-level defer? In a tight loop you'd want your cleanup to happen after the fact. And in, say, an IO loop, you're going to want concurrency anyway, which necessarily introduces new function scope.
> In a tight loop you'd want your cleanup to happen after the fact. Why? Doing 10 000 iterations where each iteration allocates and operates a resource, then later going through and freeing those 10 000 resources, is not better than doing 10 000 iterations where each iteration allocates a resource, operates on it, and frees it. You just waste more resources. > And in, say, an IO loop, you're going to want concurrency…
Re: Defer available in gcc and clang
#227Earlier quoted context omitted.
C++ implementations of defer are either really ugly thanks to using lambdas and explicitly named variables which only exist to have scoped object, or they depend on macros which need to have either a long manually namespaced name or you risk stepping on the toes of a library. I had to rename my defer macro from DEFER to MYPOROGRAM_DEFER in a project due to a macro collision. C++ would be a nicer language with native…
Because they are all the consequence of holding it wrong, avoiding RAII solutions. Working with native C APIs in C++ is akin to using unsafe in Rust, C#, Swift..., it should be wrapped in type safe functions or classes/structs, never used directly outside implementation code. If folks actually followed this more often, there would be so much less CVE reports in C++ code caused by calling into C.
The reason why C++ is as popular as it is is in large part due to how easy it is to upgrade an existing C codebase in-place. Doing a complete RAII rewrite is at best a long term objective, if not often completely out of the question.
Acknowledging this reality means giving affordances like `defer` that allow upgrading C codebases and C++ code written in a C style easier without having to rewrite the universe. Because if you're asking me to rewrite code in a C++ style all in one go, I might not pick C++.
EDIT: It also occurs to me that destructors also have limitations. They can't throw, which means that if you encounter an issue in a dtor you often have to ignore it and hope it wasn't important.
I ran into this particular annoyance when I was writing my own stream abstractions - I had to hope that closing the stream in the dtor didn't run into trouble.
Re: Defer available in gcc and clang
#228Earlier quoted context omitted.
Using a "work queue", i.e. a channel would still have a for loop like for filename := range workQueue { fp, err := os.Open(filename) if err != nil { ... } defer fp.Close() // do work } Which would have the same exact problem :)
I don't see the problem. for _, filename := range files { queue or more realistically, var group errgroup.Group group.SetLimit(10) for _, filename := range files { group.Go(func() error { f, err := os.Open(filename) if err != nil { return fmt.Errorf("failed to open file %s: %w", filename, err) } defer f.Close() // ... return nil }) } if err := group.Wait(); err != nil { return fmt.Errorf("failed to process files: %w"…
// Spawn workers
for _ := range 10 {
go func() {
for path := range workQueue {
fp, err := os.Open(path)
if err != nil { ... }
defer fp.Close()
// do work
}
}()
}
// Iterate files and give work to workers
for _, path := range paths {
workQueue Re: Defer available in gcc and clang
#229Earlier quoted context omitted.
I don't see the problem. for _, filename := range files { queue or more realistically, var group errgroup.Group group.SetLimit(10) for _, filename := range files { group.Go(func() error { f, err := os.Open(filename) if err != nil { return fmt.Errorf("failed to open file %s: %w", filename, err) } defer f.Close() // ... return nil }) } if err := group.Wait(); err != nil { return fmt.Errorf("failed to process files: %w"…
I think they imagine a solution like this: // Spawn workers for _ := range 10 { go func() { for path := range workQueue { fp, err := os.Open(path) if err != nil { ... } defer fp.Close() // do work } }() } // Iterate files and give work to workers for _, path := range paths { workQueue
Now we know why it was painful. What is interesting here is that the pain wasn't noticed as a signal that the design was off. I wonder why?
We should dive into that topic. I suspect at the heart of it lies why there is so much general dislike for Go as a language, with it being far less forgiving to poor choices than a lot of other popular languages.
Re: Defer available in gcc and clang
#230Earlier quoted context omitted.
I think they imagine a solution like this: // Spawn workers for _ := range 10 { go func() { for path := range workQueue { fp, err := os.Open(path) if err != nil { ... } defer fp.Close() // do work } }() } // Iterate files and give work to workers for _, path := range paths { workQueue
Maybe, but why would one introduce coupling between the worker queue and the work being done? That is a poor design. Now we know why it was painful. What is interesting here is that the pain wasn't noticed as a signal that the design was off. I wonder why? We should dive into that topic. I suspect at the heart of it lies why there is so much general dislike for Go as a language, with it being far less forgiving to po…