1.  

    The screen is great! Everything looks sharp, colors are vibrant and brightness is good.

    But no HDR. While iPads have had HDR for 4+yrs. I find myself missing the HDR.

    Not enough RAM for local software development

    You gotta be fucking kidding me. WTF. It is a sad state that our industry is in.

    I am very grateful that this has not been my experience.

    1. 1

      I recall asking for this feature around 13 years ago. It is nice to see that it is finally in there.

      1. 1

        congratulations, you’ve recreated CGI-BIN

        1. 12

          The readme.md mentions its CGI inspiration.

        1. 5

          I seem to remember that there wasn’t much point in using anything faster than a P66 in the Pentium Overdrive slots for most 486 motherboards because the DRAM was so much slower than on a Pentium board that a faster CPU just meant it waited forever. Intel apparently did an experiment in their whole-platform simulator around the time they started working on the Pentium 4 to see what the upper bound on system performance was with just CPU speedups: they made the simulated CPU retire any instructions it had available in zero simulation time and found that they got roughly double the performance of their current fastest CPU: it didn’t take much to move the bottleneck to some other component. I suspect this is partly why they gave up on the Overdrive lines: the set of people who were willing to take a computer apart to install a new CPU but weren’t willing to install a new motherboard was pretty high.

          On the other hand, the Pentium was about the time when the rest of the system started to be more standardised. You could take an IDE drive to a newer motherboard easily. You probably needed to buy new RAM, but everything else in the system was either on-board or interfaced over standard connections (though having to throw away that SoundBlaster AWE32 when the new motherboard stopped including ISA slots made some folks sad). The other big change around the time of the Pentium was that motherboard sizes and PSU connections were standardised, so you could replace the motherboard without replacing the case. This has been somewhat reversed by laptops where the motherboard, CPU, RAM, screen, and case are largely treated as a single unit for upgrade purposes.

          1. 1

            In your second paragraph, this was not my experience.

            The Pentium was around the time when many OEMs stopped following standards. Rather than AT or ATX PS or Mobo, they did their own thing to save money. IDE drives had already been standard for a few years and probably more like 5yrs+ when Pentium became mainstream. All that said, I do recall the AWE32 woes, but IIRC that was 4-6yrs after these overdrive chips were commonplace.

            1. 2

              You might be right. My Pentium-compatible (Cyrix P166+) machine was the first computer that I owned built out of standard parts and most of the 386 and 486 systems I saw were from vendors who used custom motherboards and cases. The ATX spec wasn’t released until 1995, I don’t think I ever saw a 486 ATX case. My machine had an AT motherboard, so the AT to ATX switch was the thing that meant I had to buy a new case, but most of the I/O was on ISA or PCI boards.

              Most earlier systems I’d used had on-board graphics (if you were lucky, it was EGA or CGA, but in a lot of systems it used some non-standard signal and connector and worked only with monitors from the same vendor), with graphics cards on expansion boards only ever an after-market add-on. IDE was pretty common by the 386 and ubiquitous for the 486, but older systems used a variety of disk interfaces. For a while the easiest way of expanding your disk was to buy an ISA ‘hard card’: a full-length ISA card with a disk controller and the disk physically mounted on it.

              My P166+ had an S3 ViRGE PCI graphics card, on-board IDE, a cheap SoundBlaster clone (ISA). It came with an IDE disk and CD-ROM drive and EDO RAM. When I upgraded the CPU I needed a newer motherboard because the voltage changed and I couldn’t find the external voltage regulators that you could apparently buy for mine to get it working but I was able to replace the motherboard without changing anything else. I think I had three, possibly four, different motherboards in that machine. It ended up as a 350MHz K6-2, with the same case, disk (+ a second one), monitor, graphics card, and optical drive.

              My next big upgrade was a 550MHz Pentium III. This time in an ATX case, which took the CD-ROM drive and disk from the old machine, along with a couple of the expansion cards (My VooDoo^2, for example). I upgraded this machine to an overclocked Athlon (1GHz, ran at 1.33 GHz). I had replaced the ISA sound card with a PCI one at some point along the way. I eventually replaced the PCI S3 ViRGE with an ATi All-In-Wonder 128 (which was about as fast for 3D as my VooDoo^2 and let my watch TV on my computer, which was important in a tiny student room). I later replaced it with a Radeon 8500.

              Every upgrade from the 133MHz (pretending to be 166MHz) Cyrix 6x86 to the 1.33GHz Athlon was incremental. I didn’t buy a complete new computer at any point. At LAN parties, there was always a bit of trading as people who’d upgraded things passed on older parts to other people and we all did lots of small incremental upgrades. This may have happened with 486 and 386 machines, but I don’t think I ever saw it. I had a couple of salvaged 386 motherboards as a wall display and they were very bespoke things.

              My next computer after that was a G4 PowerBook, so I stopped paying attention to PC hardware for a bit. I built a couple of NAS boxes since then and recently upgraded one (which I originally built 10 years ago) from a dual-core 1.6GHz AMD E-350 with 8 GiB of RAM to a quad-core Pentium Silver 5040J with 64 GiB of RAM, in the same case, with the same disks, so it looks as if motherboard upgrades are as easy as they were 20 years ago.

          1. 12

            @hwayne, “Sup nerds” is my opening, dammit.

            1. 10

              Clearly, ‘sup nerds’ is not meant here in its commonplace profane meaning: it is intended as a opening invocation akin to the Homeric ‘Muse, sing me’, and in this modern, altered, form calls down the blessing of 2500 years of dieting scholars (‘sup nerds’).

              1. 1

                Are you anna kendrick’s character from pitch perfect?

                1. 5

                  pretty sure that literally no one has ever asked this.

                  1. 5

                    I’m sure the guy in charge of SEO did…

                  1. 3

                    looking forward to that Oberon+ compared to Oberon-2 blog post, and hopefully come comparisons to modula-3 as well.

                    1. 2

                      Here is the link: https://oberon-lang.github.io/2021/07/16/comparing-oberon+-with-oberon-2-and-07.html; there are also other post about design details. Modula-3 is a quite different and more complex (and more old-fashioned) language.

                    1. 1
                      	for {
                      	// Check if context canceled or ShutdownFunc finished
                      	select {
                      	case <-c.Done():
                      		// Context canceled, return
                      		return
                      	default:
                      		if done {
                      			// ShutdownFunc finished, return
                      			return
                      		}
                      		// Otherwise continue
                      	}
                      

                      looks like a spin wait and will max out a core until the function is done or the context is canceled. Or am I reading it wrongly? I try to avoid these, even if they are unlikely to last very long. e.g. a web server being attacked slowloris style might take a long time to shutdown and this spin wait would waste a lot of CPU resources on a server that might be starved anyway because of the attack. Maybe better to not have a default in the select and to use a channel instead of done bool.

                      1. 1

                        Should I add a time.sleep() with a few milliseconds maybe? 🤔

                        Feel free to send me a better solution 😊

                        1. 2

                          Here is a diff:

                          diff --git a/shutdown.go b/shutdown.go
                          index 2d02836..35d87c9 100644
                          --- a/shutdown.go
                          +++ b/shutdown.go
                          @@ -29,25 +29,20 @@ type ShutdownFunc func()
                           
                           // Internal method
                           func (f ShutdownFunc) execute(c context.Context) {
                          -       done := false
                          +       done := make(chan struct{})
                                  // Execute ShutdownFunc in goroutine and set done = true
                                  go func() {
                                          f()
                          -               done = true
                          +               close(done)
                                  }()
                          -       for {
                          -               // Check if context canceled or ShutdownFunc finished
                          -               select {
                          -               case <-c.Done():
                          -                       // Context canceled, return
                          -                       return
                          -               default:
                          -                       if done {
                          -                               // ShutdownFunc finished, return
                          -                               return
                          -                       }
                          -                       // Otherwise continue
                          -               }
                          +       // Check if context canceled or ShutdownFunc finished
                          +       select {
                          +       case <-c.Done():
                          +               // Context canceled, return
                          +               return
                          +       case <- done:
                          +               // ShutdownFunc finished, return
                          +               return
                                  }
                           }
                          
                          1. 2

                            Thank you! I’ll merge that 😊

                          1. 3

                            Also https://matomo.org/ and https://usefathom.com/ are popular alternatives

                          1. 3

                            I run an imap server on my home server and use mail.app to drag messages from my Archive folder to an Archive folder on that home server. The home server is then backed up to cloud storage using restic.

                            1. 1

                              http://pycurl.io works REALLY WELL at this too. ;)

                              1. 6

                                http://ai./ also works. also, could you technically run a web server on one of the DNS roots? in which case, http://. could serve traffic.

                                1. 2

                                  safari can’t find the server

                                  same for pn.

                                  Maybe my pihole is blocking it.

                                1. 3

                                  raise your hand if you’ve written before.

                                  1. 2

                                    what

                                  1. 1

                                    this value of err is never used (SA4006)

                                    I’ve grown to love staticcheck when writing Go.

                                    Amazing article. I’m very happy that I rarely have to think about these details when writing code.

                                    1. 4

                                      For the optimized Go implementation:

                                      To reduce the allocations, we’ll use a map[string]*int instead of map[string]int so we only have to allocate once per unique word, instead of for every increment

                                      Huh? Can someone explain this to me? Frankly, this seems bananas.

                                      1. 7

                                        Well, let’s test it. Here’s a patch that switches to map[string]int:

                                        diff --git a/optimized.go b/optimized.go
                                        index 74b42c3..638b694 100644
                                        --- a/optimized.go
                                        +++ b/optimized.go
                                        @@ -11,7 +11,7 @@ import (
                                         func main() {
                                                offset := 0
                                                buf := make([]byte, 64*1024)
                                        -       counts := make(map[string]*int)
                                        +       counts := make(map[string]int)
                                                for {
                                                        // Read input in 64KB blocks till EOF.
                                                        n, err := os.Stdin.Read(buf[offset:])
                                        @@ -71,7 +71,7 @@ func main() {
                                        
                                                var ordered []Count
                                                for word, count := range counts {
                                        -               ordered = append(ordered, Count{word, *count})
                                        +               ordered = append(ordered, Count{word, count})
                                                }
                                                sort.Slice(ordered, func(i, j int) bool {
                                                        return ordered[i].Count > ordered[j].Count
                                        @@ -82,15 +82,8 @@ func main() {
                                                }
                                         }
                                        
                                        -func increment(counts map[string]*int, word []byte) {
                                        -       if p, ok := counts[string(word)]; ok {
                                        -               // Word already in map, increment existing int via pointer.
                                        -               *p++
                                        -               return
                                        -       }
                                        -       // Word not in map, insert new int.
                                        -       n := 1
                                        -       counts[string(word)] = &n
                                        +func increment(counts map[string]int, word []byte) {
                                        +       counts[string(word)]++
                                         }
                                        
                                         type Count struct {
                                        

                                        Before:

                                        $ hyperfine './optimized-go < kjvbible_x10.txt'
                                        Benchmark #1: ./optimized-go < kjvbible_x10.txt
                                          Time (mean ± σ):     270.4 ms ±   7.6 ms    [User: 263.5 ms, System: 11.6 ms]
                                          Range (min … max):   265.0 ms … 291.8 ms    11 runs
                                        

                                        After:

                                        $ hyperfine './optimized-go-mapstringint < kjvbible_x10.txt'
                                        Benchmark #1: ./optimized-go-mapstringint < kjvbible_x10.txt
                                          Time (mean ± σ):     453.6 ms ±  20.5 ms    [User: 486.5 ms, System: 24.2 ms]
                                          Range (min … max):   426.7 ms … 485.8 ms    10 runs
                                        

                                        Running both programs with perf shows that the program with map[string]int has a hotspot in runtime.mallocgc.

                                        Presumably, the simpler map[someString] += 1 variant allocates a new string? Using a *int allows you just do a lookup and increment the value. I guess if you think of map[x] += 1 as map[x] = map[x] + 1 then maybe the extra alloc makes a bit more sense. But I wonder whether it’s possible for the compiler to optimize this kind of case. Would be neat.

                                        1. 2

                                          The take away for me is that map as a generic type is lying to me. It is not like a generic Dictionary<K,V> in .NET where a value type is stored as part of the dictionary, at least I think it is stored that way. It is possible I don’t understand that data structure either! Instead, it seems the map always uses pointers as values. It simply fetches those values and dereferences them for me.

                                          1. 3

                                            How the Go runtime implements maps efficiently (without generics) gives some details on the implementation.

                                          2. 2

                                            Looking at the profile in the original article, it’s plain that runtime.mapaccess2_faststr is about twice as fast as runtime.mapassign_faststr. So using an *int value seems to primarily avoid calling mapassign over mapaccess2 more than anything else.

                                            Running both programs with perf shows that the program with map[string]int has a hotspot in runtime.mallocgc.

                                            That’s what the original article indicates as well. I just don’t understand why that would be. That indicates mapassign allocates memory for every call, even when the key is found. But taking a cursory look at the source for mapassign_faststr, I don’t see any allocations in the existing key path. Do you still have stack traces for the runtime.mallocgc in the slower map assign version?

                                            1. 3

                                              Yes, I agree it is perplexing. Here’s a screenshot of my profile: https://imgur.com/a/EzzUULV

                                              Unfortunately, it looks like there aren’t any traces. Usually perf report shows them in my Rust programs, so maybe Go isn’t emitting the right dwarf info for it to work.

                                        1. 2

                                          Can somebody compare with Java threads too :P

                                          1. 3

                                            If you’re going to do that, make sure to do Loom fibres as well

                                            1. 2

                                              Hmm. Are Java threads that far an abstraction above OS threads?

                                              1. 2

                                                no, they aren’t.

                                            1. 16
                                              let (|DivisibleBy|_|) by n = if n%by=0 then Some DivisibleBy else None
                                              let findMatch = function
                                                | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"
                                                | DivisibleBy 3 -> "Fizz"
                                                | DivisibleBy 5 -> "Buzz"
                                                | _ -> ""
                                              let fizzBuzz n  = n |> Seq.map findMatch |> Seq.iteri (printfn "%i %s")
                                              [<EntryPoint>]
                                              let main argv =
                                                fizzBuzz [1..100]
                                                0 // we're good. Return 0 to indicate success back to OS
                                              

                                              Look ma, no ifs or switches!

                                              I’m kinda confused how this qualifies as “no ifs or switches” when there’s an active pattern with an if expression right there on the first line that’s used multiple times.

                                              1. 3

                                                Seq.iteri

                                                It’s also buggy, as this appears to output the indices of the input sequence rather than their values

                                                1. 1

                                                  It is a lie.

                                              1. 2

                                                OMG Desqview… that is something I haven’t heard in a very long time.

                                                1. 4

                                                  working on staying away from computer screens

                                                  1. 3

                                                    It’s not working :D