-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Concurrency #17
Comments
I think generators and co-routines are the way to go. Everything should be eager unless specifically lazy. That one was easy :-) |
So are you okay with not doing anything in the language for stackless co-routines? The stackless form can implemented with a continuation monad library combined with declared purity? Edit: I have hence realized that monads are employed to enable data race safe sharing of mutable state emulated by copying immutable data, and has nothing to do with the choice of the underlying mechanism of the usermode (i.e. not kernel context-switching) cooperative multitasking. Monads enable controlling stateful effects within a pure, referentially transparent, immutable paradigm. |
Stackless co-routines are cool. Its basically the same as JavaScript's event model. If we are compiling to JavaScript we have to cope with this. If we write an infinite loop, JavaScript requires returning to the event loop to process IO. Is it best to give programmers a model where they write a linear synchronous program, and its is converted to the event model underneath and runs on a stackless co-routine runtime (transpiling to 'Go' would be interesting), or is it better to give them an asynchronous event based model? |
@keean wrote:
But they have compositional (and perhaps other) trade-offs. They seem to force either low-level tsuris, or monadic tsuris. Remember in general that monads don't compose (Applicatives do compose).
I believe that may be incorrect. JavaScript is using afaik stackful event loops (as in multiple event loops running concurrently, one each for each JavaScript instance). Afaik JavaScript is not using a jump in CPS style, rather the script must exit or control is passed to the event loop with generators and returning a Promise. Edit: I have hence realized that JavaScript’s callback model unwinds the stack storing the callback context on the heap to return to the event queue scheduler. So there is only ever one stack in use.
This is accomplished with generators and Promises (or setTimeout <--- notice camel case), which afaik are stackful.
Afair, you posed that thought to me that before on the Rust forum. Can you find my response? I find it arduous to locate details of our past discussions on Rust's forum.
|
What do you think about the actor model? |
I remember you explained some aspects of Go and especially the concurrency model are applicable to a some use cases. And I recall I didn't (at that time) find those use cases compelling for the programming I need to do right now. I am hoping (pie-in-the-sky right now trying to resolve my health ailment) to build a mobile app platform (headed towards abstracting away the operating system to a large degree); also I need to do a lot of coding for JavaScript for client and server reusing the same code base on both (i.e. Node.JS), so I need high-level abstractions, modularity, extensibility, and composition. If I were doing systems programming, I'd have different priorities. When I need to drop down to systems level tsuris, I'll probably be hoping for something better than C++, but seems Rust may have blown that chance with the mandatory lifetimes (or maybe that is exactly the feature needed if people will use it). I might have a different perspective on Go for that use case. We’ll see. As for Actor model, that is more than I want to tackle today. I'll come back to it. |
I'm a big fan of the actor model. Just as an aside. Anyway, I think CPS with tail optimization basically gives you the best possibilities, combined with syntax that compiles into a stack machine (ES6 generators work like this and async/await is just a particular generator). I really really love JavaScript's concurrency model with setTimeout. Basically, in JavaScript doesn't use coroutines, it uses a polling based event loop. A game loop in JavaScript is written like this: function loop() {
console.log("ping!");
setTimeout(loop, 0);
}
loop(); This means: run loop, then yield to the event loop and ask it to rerun loop as soon as possible. During the execution of the loop, no other code can run (not even UI events, there's no interrupt events in JavaScript). So when you write an infinite loop, you have to write a recursive loop that yields to event loop with setTimeout instead. Notice that giving it a timing of 0 means basically just infinite loop (0 means loop as fast as possible), but after each iteration, it allows the UI logic to update. I personally think this is THE best system I've ever seen in a language. It makes sure that you get concurrency, without state issues. For lots of concurrency use cases, you don't actually need threading and JavaScript's model allows you to do it very elegantly. So yeah, sticking to that same model seems logical to me. |
We could have a first class actor model:
The idea is the actor can have state but all the 'methods' must be pure, and we model calls as asynchronous message passing (which can be optimised to a normal function call. |
I'm not sure if I agree with syntax, but yeah, first class actors would be extremely interesting to have. I think actors also combine very elegantly with generators and iterators for a VERY powerful concurrency model. Combined with a yield to event loop syntax, this would make ZenScript close to the most powerful concurrent language on the market. I just wonder if we can somehow marry records and actors under the same system, so all actors are records or all records are actors. I'd also like to get delegation into that system. I'm not very familiar yet with the current ideas on records, so I'll have to read up on that and come back to you. |
Records are data, so would be the messages sent between actors, or in the internal state of actors etc. So actors are not records, actors receive and respond to messages (they act), records are just plain data (although in theory a message could contain an actor address, or even a serialised actor). So we end up with something like actors can contain records, and records can contain actors, but actors are not records. |
I'd argue you can view a record as an actor that responds to queries (messages) for data in a specific format, but I get your point. We can always look into this issue later, it's not very critical. It'd also devolve into a discussion about non-deterministic arbiters and such, which is also something I don't want to get into. In summary: first-class actors that are encouraged and influence library design = good. I envision libraries that offer lots of functionality through monadic abstractions over actors in a way that makes it look like any other language and I smile 😄 |
@SimonMeskens wrote:
There’s still the potential for data race unsafety for the mutable data shared between your loop and any other event handlers that run every time your loop does a And the callback you provide to Regarding my comment post about Actors (← click for link to my definition of unbounded nondeterminism), I found the following interesting: https://youtu.be/nXYGPYnCokE?t=541 He makes a very interesting point that aspects of models which do not preserve bijective ("bidirectional") structural equivalence, are thus introducing the aspect of thermodynamics that processes are irreversible. What he really should say is such a model doesn't require a total order and allows partial orders, which is entirely necessary for modeling asynchrony. A total order is bounded, whereas the set of partial orders is unbounded. https://youtu.be/nXYGPYnCokE?t=1460 The statement that "forgetting is the essence of thermodynamics consequences" is from the perspective of the any relative observer inside the system, but that doesn't mean the information no longer exists from the perspective of a total observer outside the system. The existence of a total observer can't be required in the system without bounded nondeterminism. So unbounded nondeterminism requires forsaking omniscience. As I had mentioned in the past, universal omniscience requires a non-quantifiable speed-of-light (so that any observer can be total in real-time), which would collapse the light cones of special relativity rendering the past and future indistinguishable and nothing distinguishable could exist. |
More on comparing Actor model to π and rho calculi: https://youtu.be/oU4piSiYkE8?t=488 The Actor model doesn't have an explicit algebra of channels (channels apparently require additional overhead because expose a race condition of the concurrent processes which can access the channels), although channels can be created in the Actor model by creating a new child Actor for each channel, where the "child" relationship is maintained in the state internal to the involved actors in terms of the child actor forwards all received messages to the parent actor. Thus apparently the π and rho calculi algebraically model this particular configuration of the Actor model. So the distinction is what can be analyzed algebraically in the model and not an enhancement of computational power of the model. An analogy is I guess the NAND or NOR gates from which all digital logic can be constructed, yet we don't program in such a low-level model. So the Actor model is fundamental but more low-level than the π and rho calculi. |
Please check my logic, facts, and analysis in this comment post. Go's green threads (much more lightweight than OS threads) are both more and less performant than JavaScript's callbacks (i.e. For example, when a stack of nested functions is waiting on the result of the innermost function, Go's very low cost context switch which retains the stack is more performant than unwinding the stack and essentially copying it into a nesting of Vice versa, when parallelizing independent code paths¹ (irrespective to scheduling algorithm) to make multiple blocking operations concurrent, Go needs a goroutine and thus an allocated stack for each concurrent operation; whereas, JavaScript only needs to allocate a An example of this latter case is invoking Hardware thread scheduling with JavaScript is afaik currently non-portable (e.g. no Web Workers on Node.js) and less flexible (e.g. ¹ Rob Pike states an incorrect definition of concurrency in the general case because in general there is no way to prove that nondeterministic composition of code paths are independent because this would require a total order which is the antithesis of unbounded nondeterminism, a.k.a. in the indeterminism case (where the unbounded entropy of the universe is intertwined in the state machine of the program), i.e. it can't be proven that the state interactions in concurrency are faultless or fault tolerant. Sean Parent provides a different definition. John Harrop’s definitions of concurrency and parallelism seem to jibe with mine. And Pike's error is also a weakness of Go's CSP (communicating sequential processes, i.e. channels) in that without bounded nondeterminism then it is impossible to guarantee the channel communication won't deadlock as I wrote before:
Even though concurrency in general can potentially deadlock on any dependent state:
|
Go is correct that the first use case example from my prior comment can be always be handled by the language and appear to the programmer at the source code level to be a synchronous function call. That assumes the programmer intends to not execute any of his source code concurrently while waiting for the result of the said blocking function. The programmer could convey this intent by assigning or casting the result value to the value type of the However, the only reason the programmer needs access to So I come back to my interpretation of the generative essence of Sean Parent's thesis, which is that the more completely and accurately we can express our algorithms, then less fragile optimization kludges will creep into our code. In other words, the programmer's job is to convey the algorithm and the compiler's (and runtime's) job is to optimize it. In other words, if the compiler can infer the parallelism, then it is free to optimize the number of threads dynamically (and even perhaps to choose between an event loop or green threads on a case-by-case basis). Compilers and runtimes thus become smarter and programming becomes simpler with more algorithmic clarity (less implementation details and premature optimization clutter). Thus I am asserting that both JavaScript and Go have some catching up to do with Haskell which I've read it already quite good at optimizing and inferring parallelism and other optimization expressed in the algorithm. And Haskell is too obtuse and as I explain below that 100% purity is too restrictive (as we discussed in the thread on purity it forces purity everywhere as total order, requiring monads to model effects as types yet monads aren't generally composable). There is hopefully a better balance that could be achieved. A marriage of pure, declared side-effects (effects as types?), and read-only declarations with an imperative language and without all the category theory when it isn't necessary. Sometimes it is necessary to borrow the lifetime, because just declaring an object read-only is insufficient to convey the algorithm. For example, for inferring the parallelism if we know the input tree object to the Another advantage (in addition to the aforementioned simplicity, clarity, and antifragile flexibility of the source code level expression of the algorithm) of having the compiler infer the parallelism versus the programmer manually encoding the parallelism, is it won't infer it incorrectly due to human error thus creating a bug. One of the main goals of compilers is to do the tedious and complex checking for correctness that humans don't naturally do perfectly. We need humans for the creativity of algorithms, not the pedantic repetitive mundane tasks and premature optimization. |
If I understand correctly the stance of those (e.g. @keean in private communications) who would argue against the viability of my prior comment, they argue that such optimization is equivalent to a proof search which is an exponentially hard complexity problem, e.g. optimizing a bubble sort to a parallel merge sort. But afaics, I did not propose allowing the compiler to change the algorithm. I merely proposed for the programmer to express the invariants necessary to infer the parallelism inherent in the algorithm expressed. I've read that one of the Haskell compilers is already apparently very good at this, which is enabled by its strictness w.r.t. such invariants such as referential transparency and side-effects lifted to types (monads). I agree that compilers which are expected to modify algorithms (e.g. from a bubble sort to a parallel merge sort) would be equivalent to an exponential search which is probably in one of the very intractable computational complexity classes. I am not yet clear if my proposal is also in an intractable computational class. The argument that claims all (or nearly all) of the interesting parallelizable code can be put in libraries carefully hand-tuned by experts seems to potentially leave a lot of userland code stuck with the noise hiding the underlying algorithm with all the premature optimization of not adopting my proposal. Also it means that expertly written library code becomes that much less readable in open source. I don't like the Cathedral model of s/w development. I prefer open source, readability, simplicity, clarity, and not premature optimization by a small group of experts who can disappear or totally muck it up and no one knows how to fix it. Also the point of having the compiler detect the parallelism (i.e. the independent code paths) is that it needs to be able to do that anyway in order to check that the parallelism that the concurrency that the programmer would otherwise manually encode would not violate the (lack of) parallelism (i.e. required data race safety) in the algorithm (c.f. Rob Pike's misunderstanding of concurrency versus parallelism). Thus in order to not involve human error and bugs, we need the express the invariants to the compiler so the compiler knows which code paths are independent. Without invariants, the compiler can only safely assume that none of the code paths are independent and thus there is no parallelizability in the code. The reason concurrency programming is so difficult (i.e. buggy) is because humans can't reason perfectly about all the invariants. Rust's strategy is to force us to have a total order of (provably safe from deadlocks and livelocks) compile-time “locks” (borrows) on all resources in the program including all allocated objects. But it seems to be uneconomic to force such pedantic tsuris of synchronization on all code, when the highly parallelizable code may only be a small portion of the program. Instead I have proposed that we only declare the invariants we need to, for the cases where we need parallelization. |
Parallelising an algorithm is equivalent to changing the algorithm. Modern super-scalar out-of-order CPUs already exploit the maximum inherant parallelism in code, and they do so at runtime, where there is more information about scheduling and completion times than at compile time. A compiler cannot beat the CPU at this kind of optimisation, hence the failure of VLIW CPUs in general computing. Again writing the Titanium compiler to achieve the same results at compile time as an x86-64 achieves as runtime turned out to be a hard problem. |
That is parallelizing the task of sorting, not inferring the inherent parallelization in the original bubble sort algorithm (if any).
In the example code below var i = ... // read string from a file
var j = ... // read string from the Internet
return i.concat(j)
They can't optimize the inherent parallelizability in the example above, because they don't know that the two reads are independent. Those invariants aren't expressed at the low level of machine code. You are conflating low and high-level parallelism. |
I see what you are talking about. There is no guarantee that the file and the network are not connected. Reading the file may change the result returned from the network. Such optimisations are not safe in general. The programmer needs to express whether the IO operations need to be sequential or parallel. In the above example they are inherantly sequential. Parallelising may break the programmers assumptions. There needs to be a way for the programmer to say it is safe to parallelise. Something like: (I, j) = parallel(read file..., read network) |
If the reading is declared pure, then it can't have side-effects. This should obviously be enforced.
I am not proposing in general. I am proposing only when the invariants insure the independence of the code paths.
Disagree. Seems you are missing the entire justification for (thrust of) my proposal. Now you are trusting the programmer to know all the invariants of every possible ramification in the universe, i.e. back to JavaScript and Go's model. And manually encoding the concurrency details instead of the compiler taking care of that detailed tsuris and noise. This causes bugs and makes code less comprehensible (obscures the algorithm). If instead we build up invariants via hierarchies, the same as we do for coding in general (i.e. we call functions which call other functions, i.e. a purity declaration is transitive to functions we as the programmer aren't even aware of), then the compiler does that reasoning for us. We generally don't want programmers to have to do non-local reasoning, as that is not modularity. Again I am not yet sure if my proposal is computational complexity tractable. But it is also not necessary that the compiler infer every parallelization opportunity. The programmer can still fallback to manual encoding when the compiler fails to infer. It can be a compiler technology that improves over time. I want to make some major innovations in programming language, if possible. |
The system does not know the invariants. The file that you read could trigger a network service to change the data downloaded from the network, such that changing the order from sequential to parallel will change the results output by the program. In general the programmer is the only thing with knowledge of the invariants, unless they are somehow expressed in the code. |
We don't refute a potentially correct proposal by refuting one example where the invariants could not be insured (thus could not be declared and wouldn't be inferred). That is disingenuous (noisy) discussion to not admit there are examples that can be insured. It does in some other examples. I had alluded to an example in one of my prior comments, where the compiler knows from the declared invariants that reading a file doesn't modify any objects (stored in RAM) in the code paths.
Not if the invariants insure that the network resource does not dependent on any such network services. My example presumed the invariants were insured. Tangentially, you are making the case for why in general concurrency is indeterminant and there are in general no independent code paths and thus in general concurrency is always buggy (and can't be fixed), because the entropy of the universe is unbounded. But my proposal is about limiting inference of inherent parallelism to insured invariants.
If the invariants aren't insurable, the programmer manually writing the concurrent structures isn't going to make it any less buggy. You've gained nothing except all the negative aspects (I've already enumerated) of not inferring. If the compiler can’t check the data race safety of the parallelism the programmer explicitly requests, then the same bugs can happen. I guess your point is that it’s better to at least supplement what the compiler can check with the programmer’s explicit intent to have a better chance of avoiding data race safety bugs for that which the compiler can’t check. Edit: you can make the argument that the programmer can declare that his algorithm doesn't care if the result of |
I think there are three scenarios, these must be sequential, these must be parallel, and "don't care". The problem with the above example is it is ambiguous between "must be sequential" and "don't care". Programmers used to writing sequential code will assume sequentiality. We need a way to distinguish don't care, and sequential. We both know this is not a problem for pure code, as beta reduction order is non-deterministic, but we cannot do IO from pure code (exactly because it makes evaluation order visible to external IO). |
In general IO assumed sequentiality, consider:
|
You are writing and reading from If instead you were copying that UI string to a dialog box control and then reading from an input field of that dialog, then you'd have inherent sequential code path expressed by the dependencies on the access to the dialog object resource. |
There is no ambiguity because if there are no dependencies, then it doesn't matter which order I understand what you are thinking is the programmer may have some semantics in mind which are not expressed unambiguously in the code itself. Can you think of an example? |
It only doesn't matter because you assume the file and network operation are independent. Let's assume they are dependent, so reading the file changes the result from the network service. How do I express that the order matters, even though the operations are on different 'channels'? |
Here's a more practical example, first write a file to a USB stick, and then display a message that it is safe to eject the USB device. If you arbitrarily parallelise IO then the message will appear whilst the file is still writing, and you will remove the device resulting in a corrupted data file. |
Correct programming code would not display that message until testing that the return value from file write operation did not return an error. You could argue that correct code would issue an exception on error (instead of a return value) which would bypass the code that follows the file write call. Exceptions thus make it ambiguous whether the code following the file write call is sequentially dependent on the successful execution of the write or not. This is a problem of expression due to Perhaps the most general construct would be to allow Thus semantically dependent sequential code paths become explicitly more verbose (where they would not have been) so that independent code paths can be inferred. |
Can you provide a quick summary of main findings? I don’t have time to read it. |
For me the three main issue i see with Go in the current state of things are :
for 1 & 2 Dmitry Vyukov seems to be on it, and he wants to do a system of lockless channels, and improve scheduler to care more about data locality and that sort of things, but there is no deadline or anything for it. for 3 it will probably come up at some point. And need to see that for blockchain, the level of IO concurency might not be that high, in bitcoin they don't recomand using more than 8 outbound connection in peers, or to limit the number of peers one node is connected to, to avoid centralization, and to have one node with too much outbound connection and all node connecting to it. They say having too much outbound connection will be detrimental to the proper scaling/decentralization of the network. |
I didn't finish it yet, but they discuss various aspect of what is at stake at hardware level with micro threading/concurency and all that sort of things. They discuss things like access window on the register file to avoid register spilling in function calls, system to share cache between different cores, the concept SPMD (single program multi data) like it look similar to shader thinking, ability to execute the same program on different data, and all kind of things. But didn't finish it yet :) |
I wrote:
Apparently only about 10 – 15% of the transistor budget is expended on OoO execution (percentage of TPD power budget may be higher): https://hothardware.com/news/amd-ryzen-die-shot-cache-structure-architecture-advantages-exposed https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client)#Core_2 https://en.wikichip.org/wiki/intel/microarchitectures/sandy_bridge_(client)#Core_2 So apparently there will be not much sense in removing for purposes of increasing number of cores. Although it would still perhaps be beneficial to turn it off when we don’t need it (i.e. in I/O bound concurrency) in order to conserve power consumption (important on mobile and in data centers), and when we need our code to surely free from timing attacks that might be lurking in the nondeterministic out-of-order speculation. Nevertheless the point remains that transistor budget is increasing faster than transistors can be consumed for extant core counts that would increase performance. IOW, the OoO execution and superscalar features can’t improved. So Intel and AMD are forced to use those transistors for increased core counts. So more extensive use of concurrency and parallelism is unavoidable. Does anyone know why Itanium at 32nm had double the number of cores of Xeon at 32nm? |
Bitcoin doesn’t scale. So that’s just mostly nonsense coming from them. Garbage in then garbage out. Their goal is that the mining/validating fullnodes have distributed network topology, but that has nearly nothing to do with whether it is decentralized. Perhaps it is partly them trying to maintain an illusion of decentralization in Bitcoin. The only way to scale with 0.05 second (50 ms) latency intra-shard transaction confirmations is with supernodes that handle 1000s of light client connections. Distributed EDIT: @NodixBlockchain perhaps you were referring to the P2P gossip network for communicating non-realtime information between all online nodes (both full and light). In that case, I agree that nodes should try to not all connect to same hub nodes. Yet I still maintain that doesn’t really guarantee anything because nodes can be Sybil attacked.
Scheduler tuning can proceed. The point is that the model is the correct one to pursue at the general purpose PL layer. |
Yes the gossip p2p protocol, they limit to 8 inbound connections, so in the
absolute normally each nodes doesnt need to be connected to more than 8-16
nodes for the network to works well.
If too much nodes end up connecting to a limited numbers, it can lead to longer propagation time for certain nodes, and not optimal distribution of the propagation time for tx/blocks. And it means some nodes will manage 1000 conections some other 1 or 2 which is not very well balanced.
They says optimal propagation time is around 8 peers per node.
Which doesn't prevent to retrieve and store larger bookmark of nodes, as long as they are selected randomly and that each node have a bookmark of node as large as possible, it still keep the network balanced.
They talk about this sort of issue in this thread among other things
https://hackernoon.com/the-ethereum-blockchain-size-has-exceeded-1tb-and-yes-its-an-issue-2b650b5f4f62
My idea to takle the main issue they are talking about is to have shards organized around application, as most likely each user will use only certain applications, and there are much less applications than users, so i tend to think 'natural shards' can organize around applications, and i have a little bit of an idea to organize this sort of things, if the network become too big.
But they speaks also about this sort of issue about keeping the network as balanced as possible between all peers of the network, and normally it doesn't need that much high number of connections / nodes.
And in the logic, large % of user should be running full node, and use the local RPC to use applications, so i'm not sure there is really that much great need for high level of concurency on the scale of a node.
Le sam. 2 juin 2018 à 19:17, shelby3 <notifications@github.com> a écrit :
… Note the edit:
EDIT: @NodixBlockchain <https://github.com/NodixBlockchain> perhaps you
were referring to the P2P gossip network for communicating non-realtime
information between all online nodes (both full and light). In that case, I
agree that nodes should try to not all connect to same hub nodes. Yet I
still maintain that doesn’t really guarantee anything because nodes can be
Sybil attacked.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#17 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/Ab61I9INBhJ0mxw0HnIvX2k05adwk7gwks5t4siegaJpZM4KHUwx>
.
|
@NodixBlockchain wrote:
Craig Wright’s data showed that propagation to the top 98+% of the hashrate is within roughly 1 second on average. But for the 1000s of nodes in the remaining 2%, propagation can be much slower. So hashrate actually drives the prioritization of connectivity in the network, not some rule which attempts to maximize the distribution of the network topology. And this is because as I already wrote to you, distribution of network topology has little or nothing to do with the economics and thus the centralization of control.
If you have an application all on one shard, then you have no resiliency if that shard loses liveness or is otherwise attacked.
I already told you that sort of mesh networked design for blockchain doesn’t scale latency nor throughput. And I’m not going to discuss that further with you in @keean’s Github issues threads. Please do not go further on this tangent. We should not pollute this thread with off-topic discussion of the details about blockchains. Please I asked you already if you want to discuss that then go respond to Traxo's posts on |
The main point is that for blockchain in the ideal setup where there is good repartition of nodes, the need for concurrency is not extreme, and it shouldnt put too much pressure on the Go scheduler.
Normally in a well balanced decentralized network where large % of user run their own node, each node shouldnt have to handle too much connections.
Configuration where each node should have to handle lot of connections is more centralized configuration with 1 big server for 1000 clients.
It's not even specific to blockchain, can have the same reasoning with file server, where on one side there is a node cluster on a cloud that serve all the requests, and the network scale by increasing the capacity of this cluster, and each node serve lot of files, and bittorrent where the network scale by increasing the number of nodes, and each node doesn't serve huge amount of files.
Bittorrent can serve large number of requests without any node needs an extreme level of concurrency.
It's really just about the level of concurrency that is required at a node level to handle large number of requests, not really things that are even specific to blockchain.
Le dim. 3 juin 2018 à 13:19, shelby3 <notifications@github.com> a écrit :
… @NodixBlockchain <https://github.com/NodixBlockchain> wrote:
If too much nodes end up connecting to a limited numbers, it can lead to
longer propagation time for certain nodes, and not optimal distribution of
the propagation time for tx/blocks.
Craig Wright’s data showed that propagation to the top 98+% of the
hashrate is within roughly 1 second on average. But for the 1000s of nodes
in the remaining 2%, propagation can be much slower. So hashrate actually
drives the prioritization of connectivity in the network, not some rule
which attempts to maximize the distribution of the network topology. And
this is because as I already wrote to you, distribution has nothing to do
with the economics and thus the centralization of control.
My idea to takle the main issue they are talking about is to have shards
organized around application, as most likely each user will use only
certain applications, and there are much less applications than users, so i
tend to think 'natural shards' can organize around applications, and i have
a little bit of an idea to organize this sort of things, if the network
become too big.
If you have an application all on one shard, then you have no resiliency
if that shard loses liveness or is otherwise attacked.
Please stop. This thread is not for discussing blockchains. Please I asked
you already if you want to discuss that then go respond to @traxo
<https://github.com/traxo>'s posts on bitcointalk.org or my posts on
Steemit or Medium.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#17 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/Ab61I6jxER-CW9yhjzXscOuZ8PriuuTcks5t48ZPgaJpZM4KHUwx>
.
|
Someone mentioned Clojure’s Does anyone know if they have any tricks up their sleeve for obtaining the same efficiency of goroutines on the JVM? How could they simulate Go’s dynamic stack allocation? |
From the docs |
M:N Green Threading on Java, Kotlin, ScalaThere’s actually a drop-in replacement for goroutines on Java and Kotlin named Quasar (c.f. also). The afore-linked Hacker News thread mentions some of the pitfalls of the alternative Akka actor library on Scala which ostensibly doesn’t do a CPS transformation (and instead apparently is more like JavaScript Continuation passing style (CPS)Quasar employs bytecode weaving to achieve continuations presumable in the continuation-passing style (CPS). Scala has a delimited continuations transformation compiler plugin, but I’m not so clear on whether it would be better than what I will propose below? A reason was provided for deprecating its use but the deprecation of the plugin itself was reverted. Someone has recently implemented fibers that depends on that plugin. I had attempted to explain that delimited continuations plugin in 2012. Today I found another tutorial. CPS normally requires tail recursion (c.f. also) to avoid overrunning the end of the stack. JavaScript doesn’t have tail call optimization (TCO); and given the reason Google provided for removing it and noting that the TC39 proposal to change the syntax as died on the vine, then JavaScript will probably never get TCO. TCO is sometimes referred to as tail call elimination (TCE). (Note @keean did mention stackless coroutines up-thread in 2016, but it didn’t register with me at that time, that CPS could essentially model M:N green threading. I mentioned it again as possibility in Jan 2018.) Green threads vs.
|
An idea for making a Pony-like PL more palatable for Javascript programmers. |
An example of the unnecessary mess that Scala and Java get in because there’s no native M:N green threads on the JVM: |
M:N Green Threads Are the Superior Concurrency Model?Click also the links in this post up-thread. Finishing up the research I had started about extant Scala concurrency paradigms, the Monix concept is basically lazy iterators named Seems they’re forced to jump through these hoops on Java and Scala because there’s no native M:N green threads on the JVM. Frankly I don’t see the benefit of bringing the knowledge of the lazy iterators into the semantics of the library-using programmer, such that the programmer has to deal with free monads. This seems to be that free monad effects discussion we already had. My stance seems to be that we can just have M:N green threading (with Actors) instead which makes sequential blocking code run asynchronously automatically. The lazy To me this all smells like people trying to be too clever for their own good and then ending up in a clusterfuck of complexity. Even adding I come back to the long post I made up-thread about effects (and Keean’s idea for iterating events step-by-step instead of registering for them). It seems monads are about being able to employ equational reasoning (c.f. also) in the algorithm orthogonal to the effects in the algorithm. But we’ve already discussed recently (c.f. links already provided) about how equational reasoning doesn’t really map well to unbounded non-determinism. Rather I think Pony’s Actor model with reference capabilities is what we need. Can anyone point out a myopia or flaw in my thinking about this issue? Am I oversimplifying and how so is my oversimplification detrimental? Of course everyone is free to do their own way. And I guess there is not just one correct way, because of human inertia. Yet I think a PL design benefits from some opinionated decisions so as to not have a kitchen sink of corner case complexity. |
In addition to fixing some typos (e.g.
You may want to click the link for the explanation of why that was added. |
The generative essence for the up-thread discussion of the Elm Architecture and @keean’s idea for pulling events (and his related point) instead of registering event callbacks, distills to not sharing mutable state between asynchronous (i.e concurrent) code paths. Also AFAICS my proposal below essentially obtains @keean’s point about having a modular interconnection between event dispatchers and receivers, which is also the point of Functional Reactive programming. The Elm Architecture orders event processing synchronously so that the shared data is updated before the next event is processed. Thus event listeners which are deregistered while processing an event, will not fire during nor after said processing. Event listeners which are registered while processing an event, will not fire until after said processing. “Pulling an event” is distinguished from registering a callback primarily¹ by not sharing the registration state of listeners between the code path that processes the event and the concurrent code path that sends the event. The event can only be sent while the listener is waiting for it and thus listener is not concurrently processing any other event. So what I was describing is the case where a callback is registered as an event handler. The handler is fired and so while the handler is busy which may cause the handler to deregister the event handler, the same event may fire again. There is a race condition. And this can apply to related events too since sometimes (e.g. drag and drop) we employ related events. There are many facets of data race errors that can occur in JavaScript because events continue to fire willy-nilly (not in any order) until the event handler is deregistered. What we want to do is wait for each event to be processed, then make any change to the event handlers registration state, before allowing any new events to fire. We can accomplish this with filtering on the message queue and interface the message queue to JavaScript's event handler mechanism (i.e. each handler sends a message), then filter at the message queue any events which fired non-sequentially (because the message queue is only processed sequentially by the single task of the partition associated with the message queue). Note there can be more than one partition (e.g. we might partition on UI elements such as a dialog box but I need to think about this more), so events will only be put into a sequential ordering for each partition (i.e. each partition’s message queue not to be conflated with the JavaScript's runtime message queue). So events could still be unordered if we have multiple partitions in our UI. Yet we will still prevent data races because remember the Pony reference capabilities (or copying) would be employed to share data between partitions. Also in general (not just for the case of event handler registration race conditions) we want each event to be processed completely before processing the next one, as it pertains to updating the data that the event handler mutates. And data that is shared between unordered partitions must be provably data race safe (which basically means proving it is immutable or is only accessed by one partition which is what Pony’s reference capabilities facilitate proving). Essentially this is a CSP channel paradigm. The listener updates its copy of which messages it is listening for while processing a message. The next message is only processed after completing the processing of the prior message. The sender to (an unbuffered) CSP channel can’t send until the listener is ready to receive a message on the channel. To avoid discarding unwanted messages and to start receiving wanted messages, the listener should send a message to the sender of messages when there is a change to the state of whether it wants to receive messages from that sender. If the processing of a message requires a non-blocking (i.e. delayed asynchronous) operation, the listener of messages could (instead of blocking and waiting) delegate this to another concurrent process which doesn’t share any mutable state. The listener would then receive the update from the conclusion of the concurrent process via a message which thus wouldn’t change shared state concurrently with processing another message. So we can conclude that a flaw in JavaScript’s concurrency model is that it doesn’t force the isolation of mutable shared state for concurrent code paths. Callbacks supplied to ¹ We also discussed how it might be involved with code structure employed to receive an event such as calling the event sender thus inverting the control, but that isn’t the generative essence. Need for message based sequencing of events in JavaScript
This is essentially what the paradigm I am proposing will do but in a more structured and implicit way with the message queue. Basically applying the "actor"-like (message based) paradigm to JavaScript. |
Tying all our discussions into a summary and some additional insights: |
Discussion from Quora that began in 2018… I replied today:
|
@keean brought to my attention the blog post However, I posit that cancellation can be added to The paradigmatic analog in the Go green threading model would be an asynchronous cancel channel that is passed into every function that can block, because concurrency via green threading employs (cooperative, user not kernel mode) preemption instead of employing generators to return a As a separate issue from Pony’s “actors” not really being Hewitt’s unbounded non-determinism Actor model, Go’s channel types (which enable creating channel objects willy-nilly) aren’t really Actor-like Partitions (ALPs) because in the Actor model there is only a lone receive-only message queue. Thus in a single-threaded Actor or ALP model, there’s shouldn’t be any means to pass a channel object into every blocking function — instead just send a cancel message to the Actor or ALP but then programmer has to build an infrastructure to propagate such a cancel message to all Actors or ALPs which are being waited on as defined by the Actor’s or ALP’s internal state machine. There’s also a need for the aforementioned, transitively-passed “cancel channel” or “propagation infrastructure” in an ALP model where I propose to abstract the message queue with a function call (actually an “actor-method-call”) and then block with green threading preemption — instead of modeling concurrency (akin to We had also documented in this Concurrency #17 thread that My proposed Go-like green threaded ALP concurrency model is by default composed of single-threaded ALPs. The syntactically sequential code is easier to reason about and a lot less tsuris (and in some or most cases more performant) than (c.f. also and also) |
I proposed Pony-esque |
I have a long comment at the Rust forum (some of the inspiration came from @keean linking me to Sean Parent's video presentation) and another one that I posted to another forum, which delve into the various options for concurrency and comparing them. Note the negative aspects of stackless coroutines enumerated in the second linked comment post.
Also note that my code that employs a ES6 generator to implement an emulation of
async / await
has never been tested, but I did update and correct it recently.@spion wrote a blog on why ES6 generators offer more flexibility than async / await.
Continuation-passing-style (CPS) can be employed for stackless coroutines which lower overhead (which for example PureScript supports with a continuation monad), but they are very restrictive and don't interopt well with the imperative programming that we do with JavaScript.
Concurrency is hard and we are not aiming for maximum performance of context-switches, thus I think stackless coroutines are a pita and not worth it. For someone who needs that control, let them use another language (e.g. PureScript or C++) and interopt through our FFI or JavaScript.
I need generators to support custom loaders, my emulation of async / await, and in general to interopt well with JavaScript ecosystem such as Node.JS.
Thus I think we should support ES6 generators initially. And we can look into coroutines later.
Thoughts?
The text was updated successfully, but these errors were encountered: