4 ms·
Hi astrobe_, interesting points! I disagree with you on a few things: > Looking at the C version, the argument "tile" is used once in the function, to get the
by druzyek 6y ago
Hi astrobe_, interesting points! I disagree with you on a few things:
> Looking at the C version, the argument "tile" is used once in the function, to get the pointer to the tile. The pointer could be passed directly: one less local.
The argument "tile" is an 8-bit index, which is faster to pass than the 16-bit pointer to the tile. More importantly, that index is used for things other than looking up the tile pointer, such as finding color pairs to recolor the tile. In other words, you could pass the 16-bit tile pointer directly, but you'd still need a way to correlate that pointer with its matching color data, and that system would be slower than using the same 8-bit index for both.
> x and y are passed only to calculate an address, this address could be passed directly
That would be more efficient and save a handful of cycles when the function is called. This does not answer the original criticism, however. As I mentioned on the page, x and y go away after the pointer is calculated, so I'm not counting those as variables that need to be available in the loop body. You still have over a dozen variables that you can't manage efficiently in Forth.
> t0 seems to be always 0, except when it is undefined.
Good point!
> The t_height, t_with are just offsets in the tile structure. This locals can go away too
I don't think I see your point. If you're going for performance, doing one array access and saving the value saves a lot of cycles compared to indexing into the array each iteration, which eats a lot of cycles.
> trans_row only exists to set skip_pixel, it seems one of them can go away. The logic seems to be "unless trans_row and something, do something". The C version might actually be more verbose than necessary.
You could get rid of one of them at the expense of readability. Or maybe there is a good name that would indicate both purposes.
> Finally, the edge_style complicates a lot the logic. Using one function to do different things because it is so simple to "just add another parameter" is typical in many HLL, and often result in awful spaghetti code.
I don't think this function is awful spaghetti code. Yes, the logic is complicated. I mention on the page that I encoded the data for some tiles as 1 bit per pixel to save room and decided to make rounded transparent corners in code depending on a flag stored for each tile. This function is a compromise to save memory. Otherwise, you could just encode the tile like any other and have three colors including transparency but take up 8 times more memory.
> Actually even in C some follow certain naming conventions - like #defines being all caps, member things being prefixed by m_, etc. And you can do that in Forth too. Once again, "write only" has more to do with the author than wit the language.
I really disagree on this one! All caps and so on for constants is nice but still does not tell you how many values, if any, a word pops and how many it leaves behind. That alone earns the "write only" label. For example: CONST_A m_foo CONST_B m_bar. What does m_foo return and what arguments does m_bar take?
Thanks for the comments!
- astrobe_ 6y agoTo be clear, I don't think this function is spaghetti code. I believe you are victim of premature optimization. You knew Forth would be slow because interpreted, so you feared for performance. Fear often makes you do the wrong things. The nice thing about Forth is that speed is often correlated to the number of words in a definition, so you can start writing for source/memory compactness all while partitioning the program into meaningful words as much as you can. Then assess the situation on the performance side. > I really disagree on this one! All caps and so on for constants is nice but still does not tell you how many values, if any, a word pops and how many it leaves behind Just like in Lua or Go you cannot tell how many values a function returns, and in most dynamically typed languages, what are the types of the input arguments. Furthermore the name alone cannot tell you about the behavior for corner cases either, like what happens if you pass a null pointer as a second argument to a string concatenation function: no-op or crash? You cannot tell until you check out the docs or read the source of the function. Chuck Moore even claims that "stack pictures" (comments describing the stack effects of a word) are unnecessary; the effects should be obvious from the definition or if it needs more elaborate commentary, it should be documented somewhere else. Documentation is something people don't take seriously. When you see that the whole documentation of a project consists in Doxygen files this is usually a bad sign. It might be pretty and nice at first glance, but there is more to documentation than documenting the interface of functions and dropping a few lines of introduction on top of that. Documentation should tell a story. And for this story to be well told, you cannot follow the layout of the code. So to answer your question, what a word does should be obvious from the name or from its definition. Someone said naming things is one of the two hard problems in CS. This is where Forth shows you the real problems and demands thoughtful solutions. 47 characters-long names are certainly not the solution. Documented naming conventions and making sure a word does not do too many things are.
- druzyek 6y ago> I believe you are victim of premature optimization. You knew Forth would be slow because interpreted, so you feared for performance. Fear often makes you do the wrong things. As I explain on the page, the Forth I'm using is not interpreted. It generates STC, and if the body of the word is smaller than a particular size which you set, it will inline the code. Someone measured the dispatch overhead for fetching the next word in FIG-Forth for the 6502 at over 80 cycles. STC only needs 12 for a JSR/RTS pair and potentially 0 if the word is inlined. In any case, if I'm trying to make each version as fast as I know how, it wouldn't matter if I knew that it was interpreted or not. On the other hand, I'm happy to look at any place in the source you find where it seems fear made me do the wrong thing. > The nice thing about Forth is that speed is often correlated to the number of words in a definition, so you can start writing for source/memory compactness all while partitioning the program into meaningful words as much as you can. Then assess the situation on the performance side. Speed is correlated with number of words in that it goes down when you do a lot of factoring. Admittedly this depends on the architecture and penalty for subroutine calls. I was all set up to factor all my words down to a line or two after reading Starting Forth but switched to longer but much faster words after starting this project. > Just like in Lua or Go you cannot tell how many values a function returns, and in most dynamically typed languages, what are the types of the input arguments. Furthermore the name alone cannot tell you about the behavior for corner cases either, like what happens if you pass a null pointer as a second argument to a string concatenation function: no-op or crash? You cannot tell until you check out the docs or read the source of the function. I've never used Lua or Go so I can't comment on that. This does not answer the original criticism. Sure, there are pieces of information in every language you don't get until you look at the documentation. The point still stands that you can tell what arguments a C function takes and what it's returning at a glance while you can't in Forth. > Documentation is something people don't take seriously. When you see that the whole documentation of a project consists in Doxygen files this is usually a bad sign. It might be pretty and nice at first glance, but there is more to documentation than documenting the interface of functions and dropping a few lines of introduction on top of that. Documentation should tell a story. And for this story to be well told, you cannot follow the layout of the code. Good point > So to answer your question, what a word does should be obvious from the name or from its definition. Someone said naming things is one of the two hard problems in CS. This is where Forth shows you the real problems and demands thoughtful solutions. 47 characters-long names are certainly not the solution. Documented naming conventions and making sure a word does not do too many things are. This is something different than what I'm pointing out. Yes, good naming will tell you what it's doing, but it doesn't tell you HOW it's doing it. Even if the word is kept short and doesn't do too much, the programmer has a lot of freedom in how they choose to use the stack, so even in well-written Forth, the word name gives no indication of what the word does to the stack. It's understandable then that you don't want a word taking 13 arguments, but once we get to the point where a valid argument could be made to have a word either take one item off the stack or two, we are stuck in the same morass where we can't tell what anything takes or returns. astrobe_, I picked DrawTile1bpp as an example where Forth is especially unwieldy. Would you like to rewrite it as an example? Maybe if someone experienced like you took a swing at it, it would better reflect what Forth can do on the 6502.