Hacker Newsnew | past | comments | ask | show | jobs | submit | vgatherps's commentslogin

Yes, this is the case that I ran into as well. You have to zero memory before reading and/or have some crazy combination of tracking what’s uninitialized capacity or initialized len, I think the rust stdlib write trait for &mut Vec got butchered over this concern.

It’s strictly more complicated and slower than the obvious thing to do and only exists to satisfy the abstract machine.


No. The correct way to write that code is to use .spare_capacity_mut() to get a &mut [MaybeUninit<T>], then write your Ts into that using .write_copy_of_slice(), then .set_len(). And that will not be any slower (though obviously more complicated) than the original incorrect code.


Oh this is very nice, I think it was stabilized since I wrote said code.


write_copy_of_slice doesn't look to be stable. I'll mess around with godbolt, but my hope that whatever incantation is used compiles down to a memcpy


As I wrote in https://news.ycombinator.com/item?id=44048391 , you have to get used to copying the libstd impl when working with MaybeUninit. For my code I put a "TODO(rustup)" comment on such copies, to remind myself to revisit them every time I update the Rust version in toolchain.toml


In other words the """safe""" stable code looks like this:

    let mut data = Vec::with_capacity(sz);
    let mut dst_uninit = data.spare_capacity_mut();
    let uninit_src: &[MaybeUninit<T>] = unsafe { transmute(buf) };
    dst_uninit.copy_from_slice(uninit_src);
    unsafe { data.set_len(sz) };


That's correct.


I wish that there was a useful “freeze” intrinsic exposed, even if only for primitive types and not for generic user types, where the values of the frozen region become unspecified instead of undefined. I believe llvm has one now?

Iirc the work on safe transmute also involves a sort of “any bit pattern” trait?

I’ve also dealt with pain implementing similar interfaces in Rust, and it really feels like you end up jumping through a ton of hoops (and in some of my cases, hurting performance) all to satisfy the abstract machine, at no benefit to programmer or application. It’s really a case where the abstract machine cart is leading the horse


>I’ve also dealt with pain implementing similar interfaces in Rust, and it really feels like you end up jumping through a ton of hoops (and in some of my cases, hurting performance) all to satisfy the abstract machine, at no benefit to programmer or application.

I've implemented what TFA calls the "double cursor" design for buffers at $dayjob, ie an underlying (ref-counted) [MaybeUninit<u8>] with two indices to track the filled, initialized and unfilled regions, plus API to split the buffer into two non-overlapping handles, etc. It certainly required wrangling with UnsafeCell in non-trivial ways to make miri happy, but it doesn't have any less performance than the equivalent C code that just dealt with uint8_t* would've had.


What is the reason for explicitly tracking the initialized-but-unfilled portion? AIUI there's no harm in treating initialized bytes as uninitialized since you're working with u8, so what do you actually gain by knowing the initialized portion? I mean, at an API level you gain the ability to return a &mut [u8] for the initialized portion, but presumably anyone actually trying to write to this buffer either wants to copy in from a &[u8] or write to a &mut [MaybeUninit<u8>].


It's for the sake of providing a &mut [u8] to the initialized region for callers that cannot work with &mut [MaybeUninit<u8>] for whatever reason, eg if they wanted to use this with a std::io::Read impl. In particlar, Read impls are discouraged but not prohibited from reading the content of the [u8] given to them, so calling them with a [MaybeUninit<u8>] transmuted into a [u8] is not generally safe.


Ah right, std::io::Read, I didn't think about that. And so it needs to explicitly track the initialized portion because otherwise calling .ensure_init() every time you want to do a read becomes expensive, that makes sense. And presumably this is also why the Buffer trait from this article cannot ever replace BorrowedBuf, because Buffer is not compatible with the Read trait without explicitly initializing the buffer every time (though I suppose it could still be added on top of BorrowedBuf if you add a new method init_len() that returns the length of the already-known-to-be-initialized prefix of parts_mut()).

EDIT: adding init_len() isn't good enough, we'd need an ensure_init() method too that returns a &mut [u8], and with those we could impl Buffer for BorrowedCursor, but if Read::read_buf() took this modified Buffer trait the default impl would be very inefficient when using a &mut [MaybeUninit<u8>] and that becomes a performance footgun.


I agree, I'd go further and say I wonder why primitive types aren't "frozen" by default.

I totally understand not wanting to promise things get zeroed, but I don't really understand why full UB, instead of just "they have whatever value is initially in memory / the register / the compiler chose" is so much better.

Has anyone ever done a performance comparison between UB and freezing I wonder? I can't find one.


That assumes the compiler reserves one continuous place for the value, which isn’t always true (hardly ever true in the case of registers). If the compiler is required to make all code paths result in the same uninitialized value, that can limit code generation options, which might reduce performance (and performance is the whole reason to use uninitialized values!).

Also, an uninitialized value might be in a memory page that gets reclaimed and then mapped in again, in which case (because it hasn’t been written to) the OS doesn’t guarantee it will have the same value the second time. There was recently a bug discovered in one of the few algorithms that uses uninitialized values, because of this effect.


> same uninitialized value, that can limit code generation options

it pretty much requires the compiler to initialize all values when they first "appear"

except that this is impossible and outright hazardous if pointers are involved

But doable for a small subset like e.g.

- stack values (but would inhibit optimizations, potentially pretty badly)

- some allocations e.g. I/O buffers, (except C alloc has no idea that you are allocating an I/O buffer)


> If the compiler is required to make all code paths result in the same uninitialized value, that can limit code generation options

Can you provide (on say x86_64) an example of this, other than the case where the compiler prunes cases based on characterizing certain paths as UB? In other words, a case where "an uninitialized value is well-defined but can be different on each read" allows more performance optimization than "the value will be the same on each read".

> Also, an uninitialized value might be in a memory page that gets reclaimed and then mapped in again, in which case (because it hasn’t been written to) the OS doesn’t guarantee it will have the same value the second time. There was recently a bug discovered in one of the few algorithms that uses uninitialized values, because of this effect.

This does not sound correct to me, at least for Linux (assuming one isn't directly requesting such behavior with madvise or something). Do you have more information?


The most obvious general case (to me) is reading an uninitialized local variable in a loop. If uninitialized has to be the same value every time, you’d have to allocate a register or stack space to ensure the value was the same on every iteration. Instead, you’d don’t have to allocate anything, just use whatever value is in any register that’s handy. (By this logic you can also start pruning code, by picking the “most optimal” value for the uninitialized variable.)


I can’t find a citation, but my recollection is the problem happened with the Briggs-Torczon sparse set algorithm, which relies on uninitialized memory not changing. For performance, they were using MMAP_UNINITIALIZED (which has to be enabled with a kernel config).


But, I wonder how much it would reduce performance, if we only have to pick a value the first time the memory is read?

I would imagine there isn't that many cases where we are reading uninitalised memory and counting on that reading not saving a value. It would happen when reading in 8-byte blocks for alignment, but does it happen that much elsewhere?


if you pick a value you have to store it, and if you have to store it it might spill into memory when register allocation fails. Moving from register-only to stack/heap usage easily slows down your program by an order of magnitude or two. If this is in a hot path, which I'd argue it is since using uninitialized values seems senseless otherwise, it might have a big impact.

The only way to really know is to test this. Compilers and their optimizations depend on a lot of things. Even the order and layout of instructions can matter due to the instruction cache. You can always go and make the guarantee later on, but undoing it would be impossible.


Uninitialized memory being UB isn’t an insane default imo (although it makes masked simd hard), nor is most UB. But the lack of escape hatches can be frustrating


Anything being UB is insane to me...


only until you get deeper into how the hardware actually work (and OS to some degree)

and realize sometimes the UB is even in the hardware registers

and that the same logical memory address might have 5 different values in hardware at the same time without you having a bug

and other fun like that

so the insanity is reality not the compiler

(through IMHO in C and especially C++ the insanity is how easily you might accidentally run into UB without doing any fancy trickery but just dumb not hot every day code)


None of what you said makes any sense. You're mixing "UB" and bugs.

UB is about declaring programs invalid with a snide "don't do that", not about incorrect execution due to an incorrect specification. E.g. speculative execution running in privileged mode due to a prior syscall is just a plain hardware bug. It's not undefined behavior. In fact, the bug in question is extremely well defined.

The closest thing to reading undefined behavior is reading a "don't care" or VHDL's 'U' value from a std_ulogic and even those are well defined in simulation, just not in physical hardware, but even there they can only ever be as bad as reading a garbage value. Since a lot of the hardware design is non-programmable, there is also usually no way to exploit it.


There is no UB in hardware registers or physical DRAM, I don't think you actually have familiarity with how the hardware works if you make this claim. (Or perhaps you aren't familiar with how crazy "UB" in the sense of the ISO C documentation is)

EDIT: one could see "apparent" violation of memory consistency if say the cache subsystem or memory controller were misconfigured, however this would require both (1) you are running in kernel mode, not user-space (2) you have a bug, so GP's claim is not supported that bug-free code could encounter such a state.


> There is no UB in hardware registers or physical DRAM

This seems very sensitive to specific definitions that others might not share. DRAM is provided with a spec sheet that defines its behavior (if you write to an address, you’ll read back the same value from the same address in the future) under certain conditions. If you violate those conditions, the behavior is… undefined. If you operate DRAM with the wrong refresh timing, or temperature, or voltage, or ionizing radiation level, you may see strange behavior. Even non-local behavior, where the value read from one cell depends on other cells (RowHammer). How is this not UB?


If a C program accesses uninitialized memory (UB), it is perfectly compliant with the language spec for the compiler to reformat your hard drive and replace your OS code with crypto mining.

I'm not exaggerating by a legalistic interpretation, and I'm only slightly exaggerating in practice. UB can do some really weird, unintuitive stuff in practice on real hardware:

https://mohitmv.github.io/blog/Shocking-Undefined-Behaviour-...

The point is that this extreme UB should never happen. It was a choice of the compiler implementors, and rather than fix this, they allowed the escape hatch of UB in the spec. It would be more sensible for the compiler to say that, e.g., accessing uninitialized memory results in a nonspecified value, or even possibly multiple different nonspecified values if accessed from different threads. That captures what we expect to happen, but would be (according to C language spec lawyers) defined behavior.

In practice, it would mean that compliant compilers will ensure that any situation in which uninitialized memory could be accessed would not result in weird edge case page faults on certain architectures or whatever that could in fact lead to wacky UB situations.

This is not an unreasonable ask.


> This is not an unreasonable ask.

But isn't this exactly parallel to the Rowhammer case in DRAM? When operating at the edge of the spec, the behavior of DRAM becomes undefined. (And, of course, one challenge with Rowhammer was about /which/ edge of the spec this happened on.) In this case, writing one physical address altered the contents of other physical addresses. This is "really weird, unintuitive stuff … on real hardware." And of course we can (and do) ask DRAM vendors to not take advantage of this undefined behavior; but they do so as an optimization, allowing slightly smaller and more closely spaced DRAM cells, and thus higher density DRAM dice for the same price. Just like it's possible to work with a language with fully-defined semantics at the cost of performance, it's possible to buy DRAM with much wider specifications and more clearly defined behavior up to the edges of those specifications… at the cost of performance.

Extreme UB in both hardware and software is a choice of priorities. You may favor giving up performance capabilities to achieve a more understandable system (so do I! I do most of my work on in-order lockstep processors with on-die ECC SRAM to maximize understandability of failure modes), but the market as a whole clearly does not, in both hardware and software.


Legitimate bugs in hardware is probably out of scope for compilers. That would be an unreasonable ask, yes. But I believe it should be the job of the compiler to make sure that a correctly executed program on a reference machine strictly adhering to the architecture specs should not result in UB.

I'm not saying that a computer architecture should be UB-free. That would be awesome if it could be done, but in practice probably a bridge too far. But a compiler should map high-level directives into low-level implementations on a specific architecture using constructs that do not result in UB. This is not too much to ask.

A compiler can't reasonably protect you from rowhammer attacks. But it should guarantee that, barring hardware errors, accessing uninitialized memory has no effect other than something sensible like returning unspecified contents, or causing a memory access exception, or whatever. It should be defined up front what the behavior is, even if some of the runtime values are unpredictable.

As a more concrete example, most languages these days clearly define what happens in signed integer overflow: the thing that you expect to happen in any two's complement machine (char)127 + (char)1 == -128. C treats this as undefined behavior, and as mentioned in my link above that can cause what should be a finite loop (with or without overflow) to compile as an infinite loop. This "optimization" step by the compiler should never have happened. C resists changing this because C compilers exist for non-twos-complement architectures where the behavior would be different. IMHO the correct approach would be to require THOSE weird esoteric architectures to compile in extra overflow checks (possibly disabled with opt-in compiler flags that explicitly violate the standard), rather than burden every C developer everywhere with the mess that is signed arithmetic overflow UB.

It's a matter of expectations. Any but the most junior programmers expect that signed integer overflow will not be portable. That's fine. But they expect a sensible result (e.g. wrap around on two's complement machines), even if it is non-portable. They don't expect the compiler to silently and sneakily change the logic of their program to something fundamentally different, because UB means the compiler can do whatever tf it wants.


> Legitimate bugs in hardware is probably out of scope for compilers.

But that's exactly the point. The "bug" of RowHammer was that it occurred slightly on the "allowed" side of the envelope, at acceptably-low refresh rates. The "UB" of RowHammer and a hundred other observable effects is that, on the "disallowed" side of the envelope, the behavior is undefined. The system designer gets to choose at what probability they are on each side of the envelope, and the trade-offs are very much optimization opportunities.

Writing software in C that may exhibit undefined behavior is exactly this -- it's choosing, as a software engineer, to be on the far side of the specification envelope. In exchange, you get access to several powerful optimizations, some at the compiler level, and some at the career level (if you think that not needing to learn to use understand your language properly is at time optimization, at least).


I've edited my claim to be a bit more clear, however in the context of parent's claim we are talking about bug-free code on an non-buggy physical processor, and I think implicitly we are talking about user-mode code where one does not have the ability to alter any DRAM timing configuration registers anyway.


> There is no UB in hardware registers

There most definitely is.

In the ARM documentation this is referred to as “UNPREDICTABLE”. The outcome is not defined. It may work. It may not. It may put garbage data in a register.


I've edited/clarified the claim above. Parent did say "without you having a bug" and entering an UNPREDICTABLE state would be buggy code. Also, "maybe puts garbage data in a register" is not UB, it's a much more reasonable thing in UB (the definition of UNPREDICTABLE prior to ARMv8 does seem to allow for UB, however). I don't believe that such an UNPREDICTABLE state is reachable from user-space/unprivileged code (or else it would violate the chip's security properties) -- but if I'm wrong on that I'd be interested in an example.



Those are examples regarding UB in the C++ language not the UNPREDICTABLE state in the ARM ISA which is what I asked for examples of.


I do not find it so easy to accidentally run into UB in C if you follow some basic rules. The exceptions are null pointer dereferences, out-of-bounds accesses for arrays, and signed overflow, all those can be turned into run-time traps. The rules include no pointer arithmetic, no type casts, and having some ownership strategy. None of those is difficult to implement and where exceptions are made, one should treat it carefully similar to using "unsafe" in Rust.


Nah it makes some sense for portability between architectures. Or at least it did back when C was invented and there were some wild architectures out there.

And it definitely does allow some optimisation. But probably nothing significant on modern out-of-order machines.


> there were some wild architectures out there.

what is out there is still pretty wield

just slightly less

> probably nothing significant on modern out-of-order machines.

having no UB at all will kill a lot of optimizations still relevant today (and won't match anymore to hardware as some UB is on hardware level)

out of order machines aren't magically fixing that, just makes some less optimized code work better, but not all

and a lot of low energy/cheap hardware does have no or very very limited out of order capabilities so it's still very relevant and likely will stay very relevant for a very long time


How would you implement integer-to-pointer conversions without UB?


What is UB about integer-to-pointer conversions?


Plenty of things! The resulting pointer may not be pointing to an object that is currently live, for example. It may not even be pointing to an object that makes any sense in the language's object model. It might be pointing to a return address on the stack, for example. Or a constant in the constant pool. Or a saved register in the middle of a computation that corresponds to no variables in the original program.

In short, the moment you enable integer-to-pointer conversions (assuming your target has a flat address space), you create pointer provenance problems whose only resolution is that some things have to be UB.


I don't think any of those are undefined behavior in the strict sense in which the term is defined in the C/C++ standards. Pointer casts are defined behavior. I believe the things you point to are either implementation-defined or unspecified, which is different from UB.

It may seem nitpicky, but the downside of relying on implementation defined or unspecified behavior is largely boxed and contained. E.g you might get a memory access error. UB is, in principle, completely unlimited in downside. And because of that, it often interacts badly with optimization passes, resulting in very strange bugs.


jcranmer is correct and pointer provenance-related issues are not "boxed and contained". Start here: https://www.ralfj.de/blog/2020/12/14/provenance.html


Pointer provenance is UB in the C/C++ sense, although it's one that largely lurks in the implementation-defined behavior of "integer/pointer casts are implementation-defined." The closest you're going to find to a full specification of pointer provenance is TS 6010 (https://www.open-std.org/jtc1/sc22/WG14/www/docs/n3226.pdf), but it should be noted that no compilers actually implement provenance strictly along the lines of TS 6010. Instead, they implement a poorly-documented, buggy, internally incoherent definition of provenance...

> It may seem nitpicky, but the downside of relying on implementation defined or unspecified behavior is largely boxed and contained.

... that is incoherent precisely because the interactions of provenance with optimizations isn't boxed and contained.

It's really not until people started putting the model into formal semantics that they realized "hey, wait a second, this means either most of our optimizations are wrong or our semantics are wrong" and the consensus is that it was the semantics that were broken.


> why primitive types aren't "frozen" by default.

it kills _a lot_ of optimizations leading to problematic perf. degredation

TL;DR: always freezing I/O buffers => yes no issues (in general); freezing all primitives => perf problem

(at lest in practice in theory many might still be possible but with a way higher analysis compute cost (like exponential higher) and potentially needing more high level information (so bad luck C)).

still for I/O buffers of primitive enough types `frozen` is basically always just fine (I also vaguely remember some discussion about some people more involved into rust core development to probably wanting to add some functionality like that, so it might still happen).

To illustrate why frozen I/O buffers are just fin: Some systems do already anyway always (zero or rand) initialize all their I/O buffers. And a lot of systems reuse I/O buffers, they init them once on startup and then just continuously re-use them. And some OS setups do (zero or rand) initialize all OS memory allocations (through that is for the OS granting more memory to your in process memory allocator, not for every lang specific alloc call, and it doesn't remove UB for stack or register values at all (nor for various stations related to heap values either)).

So doing much more "costly" things then just freezing them is pretty much normal for I/O buffers.

Through as mentioned, sometimes things are not frozen undefined on a hardware level (things like every read might return different values). It's a bit of a niche issue you probably won't run into wrt. I/O buffers and I'm not sure how common it is on modern hardware, but still a thing.

But freezing primitives which majorly affect control flows is both making some optimizations impossible and other much harder to compute/check/find, potentially to a point where it's not viable anymore.

This can involve (as in freezing can prevent) some forms of dead code elimination, some forms of inlining+unrolling+const propagation etc.. This is mostly (but not exclusively) for micro optimizations but micro optimizations which sum up and accumulate leading to (potentially but not always) major performance regressions. Frozen also has some subtle interactions with floats and their different NaN values (can be a problem especially wrt. signaling NaNs).

Through I'm wondering if a different C/C++ where arrays of primitives are always treated as frozen (and no signaling NaNs) would have worked just fine without any noticeable perf. drawback. And if so, if rust should adopt this...


This isn't just about the abstract machine. This is also about making it hard to end up using uninitialized memory, which is a security hole.

Abstractions like ReadBuf allow safe code to efficiently work with uninitialized buffers without risking exposure of random memory contents.


This is already discussed for Rust: https://github.com/rust-lang/rfcs/pull/3605. TL;DR: it's not as easy as it looks to just add "freeze."


It is as easy as it looks to add `freeze`. That is, value-based `freeze`, reference-based `freeze` while seemingly reasonable is broken because of MADV_FREE.

Some people simply aren't comfortable with it.

Currently sound Rust code does not depend on the value of uninitialized memory whatsoever. Adding `freeze` means that it can. A vulnerability similar to heartbleed to expose secrets from free'd memory is impossible in sound Rust code without `freeze`, but theoretically possible with `freeze`.

Whether you consider this a realistic issue or not likely determines your stance on `freeze`. I personally don't think it's a big deal and have several algorithms which are fundamentally being slowed down by the lack of `freeze`, so I'd love it if we added it.


> Some people simply aren't comfortable with it.

Some people--especially when those people are closest to the workings of the operational semantics--not being comfortable is a sign that it is in fact harder than it looks.

The problems with "freeze" are in the same vein as integer-to-pointer semantics: it's a change which turns out to have implications for things not closely related to the operation itself, giving it a spooky-action-at-a-distance effect that is hard to tame.

The deeper issue is that, while there is clearly a use for some sort of "garbage value" semantics in a high-level language (that supports things like uninitialized I/O buffers, excessive reads for vectorization, padding bytes within structures), it's not clear which of the subtly different variants of garbage value works the best for all of the use cases.


> Currently sound Rust code does not depend on the value of uninitialized memory whatsoever. Adding `freeze` means that it can.

Arguably, the existence of "asm!() freeze" has already broken this idea. Of course, you nominally don't get any guarantees about the stability of data that asm!() code reads from uninitialized bytes, yet you can do it nonetheless.

And it's not like it's practical to say "asm!() code is always unsound if it uses uninitialized bytes like they're numbers!", since lots of it does useful stuff like communicating with the kernel with structs that get serialized, and it can also open up interfaces like mmap() which translate possibly-uninitialized virtual-memory bytes into definite kernel bytes.

Not to mention /proc/self/mem and similar kernel-provided debugging utilities that can peek into memory as serialized data.


also to be realistic in a lot of practical situations I/O buffers are reused so at least for the I/O buffer use-case it can be very viable (perf wise, in most use cases) to just zero or rand initialize it once on alloc and then treat it as frozen in all repeated usages (through it does open the issue of bug now potentially leaking previous content of the I/O buffer).

but I guess this isn't just about I/O buffers ;)



To expand on this link, this is probably the closest you're going to get to 'I'll "program" in LinAlg, and a JIT can compile it to whatever wonky way your HW requires.' right now. JAX implements a good portion of the Numpy interface - which is the most common interface for linear algebra-heavy code in Python - so you can often just write Numpy code, but with `jax.numpy` instead of `numpy`, then wrap it in a `jax.jit` to have it run on the GPU.


I was about to say that it is literally just Jax.

It genuinely deserves to exist alongside pytorch. It's not just Google's latest framework that you're forced to use to target TPUs.


True, we absolutely couldn’t allow a place that people can voluntarily participate in to say things to exist without a governing body deciding what is and isn’t allowed to be said


“Financial derivatives” and “downside is limited” should never appear in the same sentence without strong conditions on what sorts of derivatives/ combos of them


Jane Street is one of the slower market making firms and generates a significant share of revenue from being everywhere on everything (MUCH easier said than done). You want to trade some Canadian lumber ETF? Jane Street will be there. Some bond product with constituents that trade across 3 different trading sessions? Jane Street's active in that market. None of that is to say they don't have any presence in major products or don't have real short term alphas/edges of course.

They've never been at the forefront of latency games, like Jane Street isn't the firm sweeping equity markets since they have the fastest radio network out of CME (dubious value) or getting their quotes first-in-line every time.


People who don't want to X can always find a reason not to, for any given X. Somebody might even talk about how they want to do X, and go through many of the motions and preparations, but always find some reason at the end to back out.

Is Paul's statement much different from the statement "talk is cheap"?


No, but I think the more salient point to his post is to underscore the previous reasons — all of which came to naught.


No, if I had to nitpick, I'd say 'talk is expensive'. The original text doesn't say "Somebody might even talk about how they want to do X" like your comment, and it doesn't need to. But maybe someone who finds 'some reason not to do' will say it, and they won't actually do it.

(B has never said he wants to learn to code)

A: Do you have any plans to learn to code? It seems to be useful.

B: No.... (thinking) Well, AIs.

(And B doesn't learn to code in the real world)

Actually, this comments doesn't matter at all, but I leave it in for fun


Though I think it should be “for any given Y”. As X refers to the thing avoided, and Y (or any variable other than X) would refer to the excuse given.


Came here to say just this. Glad someone else saw it.


How is

> How do you justify using AI for your game art? I have thought about this, and couldn't justify it. I also know that I hold on to morals almost to a stubborn degree.

different from

> How do you justify using Blender for your game art? I have thought about this, and couldn't justify it. I also know that I hold on to morals almost to a stubborn degree.

Aside from a vague implication that it is immoral to use an AI tool instead of <some other tool> to generate your own game art?


I've thought about this, and it's true Blender allows you to take a lot of shortcuts to be creative, like displacement based on generated noise. It has made work faster, and probably cut some low paying modeling jobs, because those automated shortcuts it has do things better and faster.

The distinction is that it's a widely accepted tool, and you still need to devote time to learn to use it good enough for you to be able to make satisfying art. It's like a brush for a traditional painter, only in 3d. In another comment below I explained a bit more why I value the amount of work seen in art.


I think an important distinction in good or poor quality AI art is similar to good or poor quality models made in Blender. A modeler can spend hours or days on a model; testing different cuts and resolutions and extrusions etc to get it just right. Similarly, the difference for AI generated assets will come down to how much time the prompt engineer will spend tuning their prompts, looking into different models, possibly training models for specific styles and outcomes, regenerating over and over again to get things just right.

On the other side, you have the modeler who barely sticks six rectangles together as a “human” and the AI art prompter who ships the first image they get back.

To me, neither is a moral issue. They both require using tools effectively.


People who have some understanding of study design and data collection would be in a much better spot to understand and interpret day-to-day news / “information flood” than those who have done a lot of calculus-based probability. You can go all the way through rigorous measure-theoretical probability and come away with almost nothing useful for interpreting a study.

Most problems I see with moderns statistics aren’t of the form “ohhh, they fooled you by using a subtly wrong statistical metric to ascribe significance” but “the way the data was gathered/interpreted is fundamentally wrong and made to mislead”


Some of the problems come from statistics itself. “All models are wrong, but some are damn hard to interpret.” Let’s make that a corollary to Box’s statement about the utility of models. Why does so much statistical analysis take place without even an expectation of human comprehension? It’s just magical sigils for most papers.

Many midlevel statistical practitioners suffer from a holier than thou complex, where a “correct” approach to statistical analysis might buy a little more precision at the expense of a lot of comprehension.

Box plots or Bar charts with error bars, using randomized data collection. That’s like 90% of the interpretive value right there. Statistics is a UI for math and it could use improvement if we expect so much from it.

See Brett Victor’s “Kill Math” for more context on why we should expect more from our mathematical interfaces. http://worrydream.com/KillMath/


> "Math" consists of assigning meaning to a set of symbols, blindly shuffling around these symbols according to arcane rules, and then interpreting a meaning from the shuffled result. The process is not unlike casting lots.

Form that Brett Victor.

Wow


@dr_dshiv Edit: Sorry about the bad quote, I should have included the prefix "When most people speak of Math ...". And the negative comment about the blog post.

Now I've read / skimmed all of it and it was interesting, I hope the new methods he wants to use for teaching maths will work fine. (I'm sceptical, but still seems like worth to give it a try.)

I think his project title does his project a disservice: "Kill maths"? That sounds silly to me. And how it starts -- I got annoyed and stopped reading (until I went back two days later).

Another more positive project title maybe could be "Maths for everyone" or "A new approach for teaching maths"?


This is not true, you can run non-send futures using Tokio: https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: