xudong-huang / may Goto Github PK
View Code? Open in Web Editor NEWrust stackful coroutine library
License: Apache License 2.0
rust stackful coroutine library
License: Apache License 2.0
Hi.
To have better safety, could we use guard pages and stack probes in may
?
I'm not sure it's possible, just asking.
Thanks.
Hi.
In my quest to use may
in relm
, I've stumbled on the issue that may
coroutines require both Send
and Sync
.
My issue has to do with the fact that gtk
widgets does not implement these traits, but I think it could workaround this fact by using send-cell
and executing gtk-related code in the main thread with MainContext::invoke()
if only the Send
trait was required.
So, my question is: why is Sync
required?
Is it because a coroutine can be executed in different threads?
If it is the case, would it be possible to have a coroutine bound to a thread that only require Send
or a coroutine that Send
its state to a new thread when it's scheduled to run on a new thread (assuming it is sound)?
(Maybe this can help?)
Thanks.
A coroutine's stack can be moved from one thread to another. However, a stack may always contain things that are not Send
, breaking things.
This is one example:
extern crate may;
use std::cell::RefCell;
use std::rc::Rc;
use std::thread::ThreadId;
use may::coroutine;
use coroutine::yield_now;
thread_local!(static ID: RefCell<Option<Rc<ThreadId>>> = RefCell::new(None));
fn main() {
may::config().set_io_workers(60);
may::config().set_workers(60);
let h = coroutine::spawn(move || {
let v = (0..10000)
.map(|i| {
coroutine::spawn(|| {
let handle = Rc::new(std::thread::current().id());
ID.with(|id| {
*id.borrow_mut() = Some(Rc::clone(&handle));
});
for _ in 0..10000 {
if *handle != std::thread::current().id() {
println!("Access to Rc content without a mutex from a different thread, {:?} vs {:?}",
*handle, std::thread::current().id());
}
yield_now();
}
})
})
.collect::<Vec<_>>();
for i in v {
i.join().unwrap();
}
});
h.join().unwrap();
}
If something else was accessing the Rc
(like making copis of it) from the original thread (which it could, because it is accessible in the thread local storage), it would be undefined behaviour ‒ both the coroutine, that moved, and the thing in that thread (possibly other coroutine) could be accessing the counters in the Rc at the same time, or the data inside, which could be for example a RefCell
.
Now, suggesting not to use thread local storage doesn't solve anything, because:
Send
for other reasons. One example might be Zero-MQ sockets, which explode the whole application if ever touched from a different thread then they were created in.I believe this problem is fundamental to any attempt to move stacks between threads in Rust. Such thing just breaks the Rust contract.
So my only suggestion is to create the coroutine in one thread (it is possible to check nothing Send
crosses a closure boundary, so the closure can be safely sent to another thread) and then pin it there to that thread.
using socket2
crate instead of net2
https://github.com/edef1c/libfringe
This crate has rather nice approach to switching contexts:
It seems that this crate provides the ability to cancel coroutines, but I can't resolve how to do it. What is the general syntax for coroutine cancellation?
The project looks similar to mioco. Is it related somehow?
In any case it should be mentioned somewhere in README, maybe included in the performance comparison.
Hi.
I'm currently in the process to port my relm
crate from tokio
to may
(see this branch) and I have an issue with the http
example which is based on the may
version of hyper.
The code panic around here saying the following error:
yield type mismatch error detected, expected type: may::coroutine_impl::EventSubscriber
thread '<unnamed>' panicked at 'Box<Any>'
It seems to come from the coroutine
crate, but I'm not sure why it happens.
Thanks for your help.
replace all try!
macro with ?
operator
add unsafe
declaration for coroutine creation since it will trigger undefined behavior if user's code
ref #6
this would break current test and usage.
On latest nightly (rustc 1.29.0-nightly (9fd3d7899 2018-07-07)
) the compilation fails with
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:4:5
|
4 | use self::alloc::raw_vec::RawVec;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:44:11
|
44 | data: RawVec<T>,
| ^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:55:19
|
55 | data: RawVec::with_capacity(BLOCK_SIZE),
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:8:5
|
8 | use alloc::raw_vec::RawVec;
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:20:10
|
20 | buf: RawVec<usize>,
| ^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:26:18
|
26 | buf: RawVec::with_capacity(0),
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:41:18
|
41 | buf: RawVec::with_capacity(size),
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
Compiling num-iter v0.1.37
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:63:34
|
63 | let data = self.data.ptr().offset((index & BLOCK_MASK) as isize);
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:73:34
|
73 | let data = self.data.ptr().offset((index & BLOCK_MASK) as isize);
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:84:36
|
84 | let mut p_data = self.data.ptr().offset((start & BLOCK_MASK) as isize);
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:51:31
|
51 | let buf = stk.buf.ptr();
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:63:36
|
63 | let mut ptr = self.buf.ptr();
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:69:28
|
69 | let cap = self.buf.cap();
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:79:18
|
79 | self.buf.cap()
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:84:27
|
84 | unsafe { self.buf.ptr().offset(self.buf.cap() as isize) as *mut usize }
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:84:49
|
84 | unsafe { self.buf.ptr().offset(self.buf.cap() as isize) as *mut usize }
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
--> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:90:18
|
90 | self.buf.ptr()
| ^^^
|
= help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error: aborting due to 11 previous errors
I have this small example but I'm not able to understand why I'm getting the whole sentence after 18secs in my terminal instead of one char each second:
#[macro_use]
extern crate may;
use std::{thread, time};
use may::sync::mpsc::{channel, Sender};
use std::io::{self, Write};
struct Message(u8, Sender<u8>);
fn main() {
let (tx, rx) = channel();
thread::spawn(move || {
while let Ok(Message(byte, inner_tx)) = rx.recv() {
thread::sleep(time::Duration::from_millis(1000));
inner_tx.send(byte).unwrap(); // I expect this to wake the coroutine that's waiting on .recv
}
});
let sentence = "This is a sentence";
may::coroutine::scope(|s| {
for byte in sentence.as_bytes() {
let tx = tx.clone();
go!(s, move || {
let (inner_tx, inner_rx) = channel();
tx.send(Message(byte.clone(), inner_tx)).unwrap();
if let Ok(byte) = inner_rx.recv() {
io::stdout().write(&[byte]).unwrap();
}
});
}
});
}
Thank you for creating this high-performance library and making async easier. Reading your blog, it seems average and peak latency seems to be higher compared to tokio based implementation.
Is this due to higher throughput or scheduling coroutine?
e.g.
Tokio threaded:
Thread Stats Avg Stdev Max +/- Stdev
Latency 52.54us 56.39us 12.14ms 99.36%
vs
May coroutine:
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.26ms 6.48ms 105.13ms 98.35%
I have noticed you have disabled cast_ref_to_mut
Clippy error. I checked the code, and I found nonsensical code like the following:
// prevent release version wrong optimization!! use a volatile read
let push_index = unsafe { std::ptr::read_volatile(&self.push_index) };
This isn't wrong optimization, this is undefined behaviour caused by cast_ref_to_mut
violation.
Volatile is 100% wrong when dealing with thread safety. Please consider migrating to atomic types specifically (atomic types like AtomicPtr
are inner mutability types by the way). If you are concerned about performance, Ordering::Relaxed
is pretty much free (other than disabling optimizations that would break this code anyways), so if you don't need atomics at a given point, you can simply use relaxed ordering.
I looked into the code a bit and this is what I found:
https://github.com/Xudong-Huang/may/blob/master/src/config.rs#L25
pub fn set_io_workers(&self, workers: usize) -> &Self {
info!("set io workers={:?}", workers);
unsafe {
IO_WORKERS = workers;
}
self
}
IO_WORKERS
is defined as static mut WORKERS: usize = 2;
. There's nothing preventing the user of the library from calling this from multiple threads, which is by definition a data race (unsynchronized access from multiple threads, at least one of them is write), which is undefined behaviour:
https://doc.rust-lang.org/reference/behavior-considered-undefined.html
The function is not marked as unsafe. This means the user of the library is able to invoke UB without using any UB on his side, which is what Rust should be preventing.
Please use appropriate tools for synchronisation (either mutex or atomis).
First of all, thanks for this amazing library! I'm trying to decide which async library to use in my project (options: May and Tokio), and it seems like May latest commit was almost 2 months ago. Thus, my question is very simple: is any further development of the library planned? Thank you!
ref std::os::unix::net
could use a ReadV/WriteV extension trait for the new interface
pass &mut FnMut() as parameter for coroutine spawn, this should remove and Send bound and don't need a Box creation for the closure
currently if send an io object that initialized in coroutine context to a thread context, it will trigger a core dump. This was caused by wrongly assuming the context when initialize the io object. however detect run time context every time call read/write would hurt the performance a little.
I'm trying to understand why this small program is failing to run:
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate may;
extern crate rand;
use std::collections::HashMap;
lazy_static! {
static ref DATA: Vec<u8> = vec![rand::random::<u8>()];
}
fn main() {
let mut entries: HashMap<usize, Vec<u8>> = HashMap::new();
let task = go!(move || {
entries.insert(0, DATA.clone());
});
task.join().unwrap();
}
This program in particular gives me the error stack overflow detected, size=4096
, but if I try to do something more elaborated using shared access to entries
it also raises some address boundary errors.
The interesting part is that this single combination is failing but if I use the crates separately they work fine:
// This will work
static ref DATA: Vec<u8> = vec![123];
// Or this
let data = vec![rand::random::<u8>()];
let task = go!(...);
I'm using may 0.2.0
and rand 0.4.2
.
we can use this API to wrap the code that would run for a long time or would block a thread.
when the API is invoked, the worker thread would become a normal thread, and it will kick another thread to continue scheduling on the worker queue.
when the API is invoked in the IO thread, it would first re-scheduling to a worker thread.
when user config MAY
as following
may::config()::set_workers(1).set_io_workers(0);
MAY
should running all the coroutines on a single thread. this would
This will NOT help the TLS issue. Let coroutines bound to a single thread is still not safe, e.g. coroutine A in the thread set a TLS into value A', and then another coroutine B is scheduled and it set the TLS into value B', after coroutine A rescheduled on this same thread, it will access a dirty value!
add Debug trait impl for Blocker
Just read https://www.usenix.org/system/files/fast19-kourtis.pdf and it makes a really compelling case for the combination of stackful coroutines + proper linux AIO (and eventually SPDK support). This is a combination I could actually imagine myself using in sled, where I'm now trying to scale toward a many-core architecture, but don't want to pay the various ergonomic costs associated with the popular async stuff in the rust ecosystem right now.
https://github.com/hmwill/tokio-linux-aio may be a nice reference for building linux AIO support for May.
Would you be interested in having AIO support in May directly, or do you see this as something better implemented in a separate library? Very curious about this :)
currently all the io operations are "blocking" in coroutine context, but some times we still need nonblocking read/write that not evolve rescheduling, e.g. detect if a socket has some data available.
the API interface should be the same as std API. We need to store an extra blocking flag for the io object internally.
when cancel a coroutine we trigger a cancel panic inside it. This would cause the mutex got poisoned if it not in the wait state. A better and elegant way is to let the mutex finish normally.
this is possible when you hold a mutex guard, and wait for other resources in the coroutine. when the cancel comes, it will run the mutex guard drop in a panicking context while unwind the stack.
and this is only valid in coroutine context, we can't cancel a thread.
Hi,
And congrats for May. This is definitely a game changer, that makes it a real joy to do async I/O in Rust.
Is there currently a way to have a bounded mpmc queue? I can use a shared atomic counter, but I was wondering if there was already something available, or if you were planning to implement it.
Cheers!
Hello! When I'm writing my code like this:
#[macro_use]
extern crate may;
fn main () {
for i in 1..2000 {
go!(move || {
println!("{}", i);
});
}
}
I got a panic in the output:
// 1~989 omitted
990
991
992
993
994
thread '<unnamed>' panicked at 'cannot access stdout during shutdown', libcore/option.rs:960:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
And I cannot get the correct output, which should include all numbers in [1, 2000).
What happened? I'm using may 0.3.0 from crates.io.
This can allow some usecases:
Perhaps this is already possible?
Is there a performance comparison with go language?
It would be great if there was a Postgres client library for may. Thanks for considering!
any type that can be converted to the raw fd/handle could be wrapped in a generic wrapper type that would automatically have the non-blocking style Read/Write
in coroutine context.
//// Opens a TCP connection to a remote host with a timeout.
///
/// Unlike `connect`, `connect_timeout` takes a single [`SocketAddr`] since
/// timeout must be applied to individual addresses.
///
/// It is an error to pass a zero `Duration` to this function.
///
/// Unlike other methods on `TcpStream`, this does not correspond to a
/// single system call. It instead calls `connect` in nonblocking mode and
/// then uses an OS-specific mechanism to await the completion of the
/// connection request.
///
/// [`SocketAddr`]: ../../std/net/enum.SocketAddr.html
#[stable(feature = "tcpstream_connect_timeout", since = "1.21.0")]
pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream>
use winapi 0.3
use miow 0.3
test sync::condvar::tests::test_condvar_canceled ... test sync::condvar::tests::test_condvar_canceled has been running for over 60 seconds
I guess it has some performance benefits, but it's still a kind of mystery for me. So could you explain because of what may::net is faster than the standard std::net?
Hello @Xudong-Huang. Thanks for the amazing library. I am personally do not have knowledge to write a good coroutine implementation, so I patiently sit and wait while someone write one to use it in my projects. And I've already seen several attempts to do so with different api and different implementation details.
To properly use such coroutine library it has to have an ecosystem around with several libraries with file/networking api and other utility stuff. Is it possible to write such a client library in the abstract way, which will work with different coroutine implementations?
As I understand, if go
function will be a macro (it already is a macro in your library), and yield
function will be a macro, then a client library can just call it without having to know which specific library will provide the implementation. I expect that this specification can be made in an application, which uses the library.
So my primary question: Can we write a client library that uses stackful coroutines without having to know which exact implementation it uses?
I am trying to do HTTPS requests with MAY
, but I'm not sure that the way I do it is absolutely correct. Now my code looks like this:
use may::coroutine;
use may::go;
use openssl::ssl::SslConnector;
use openssl::ssl::SslMethod;
use std::io::{Read, Write};
type MayStream = may::net::TcpStream;
fn main() {
coroutine::scope(|scope| {
for _ in 0..100 {
go!(scope, move || {
let stream = MayStream::connect("91.198.174.192:443").unwrap();
let mut stream = SslConnector::builder(SslMethod::tls()) // (1)
.unwrap()
.build()
.connect("www.wikipedia.org", stream)
.unwrap();
let request =
b"GET https://www.wikipedia.org/ HTTP/1.1\r\nHost: www.wikipedia.org\r\n\r\n";
stream.write_all(request).unwrap();
stream.flush().unwrap();
let mut buffer = vec![0; 4096];
stream.read_to_end(&mut buffer).unwrap();
println!("{}", String::from_utf8(buffer).unwrap());
});
}
});
}
Is the created SslStream
(1) is coroutines-oriented, meaning that it uses MAY
's utilities instead of stopping the whole thread?
The current scheduler has the following problems:
I propose the following design for a new scheduler which I plan to implement. This is a request for comments. My understanding of may is not that deep so it is possible that some things won't work :-)
The scheduling loop will look like this:
loop {
// when yieldlist is not empty, this means we have coroutines the yielded but are ready to run.
// as such we do need to check the eventloop and return as fast as possible
let timeout = if !sched.yieldlist.empty() { 0 } else { sched.next_deadline() - now() };
while let Some(co) = yieldlist.pop_back() {
sched.readylist.push(co);
}
// select moves ready coroutines to the local readylist. each ready coroutine is pushed to the front
sched.eventloop.select(timeout);
// if we have more than 1 coroutine ready to run, and there are no threads stealing,
// and we have parked threads, unpark one to increase parallelism
if sched.readylist.len() > 1 && num_stealing.load(Ordering::Acquire) == 0 && !parked.empty() {
if let Some(t) = parked.pop() {
t.unpark();
}
}
// run all coros until readylist is empty
while let Some(co) = sched.readylist.pop() {
run_coroutine(co);
}
// we have cpu bound coroutines that yielded, restart the loop to make more coroutines ready/expire timers, etc.
if !select.yieldlist.empty() {
continue;
}
// we have no ready coroutines to run. time to steal!
assert!(sched.readlist.empty());
num_stealing.fetch_add(1, Ordering::Release);
// see implementation below
if sched.steal() {
continue;
}
// we didn't manage to steal anything, which means there is nothing to do.
num_stealing.fetch_sub(1, Ordering::Release);
parked.push(thread::current());
thread::park()
}
To make stealing fast and avoid spurious park()/unpark() we spin for a while trying to steal and then give up.
fn steal(&mut self) {
let deadline = now() + 100ms; // needs tuning
loop {
let id = rand() % N; // random victim
if id == self.id { // stealing from ourselves is silly :-p
continue;
} else {
let stolen = loop {
match schedules[id].readylist.steal() {
Steal::Empty => break None,
Steal::Data(co) => break Some(co),
Steal::Retry => {},
};
if let Some(co) = stolen {
self.readylist.push(co);
return true;
}
}
if now() > deadline {
return false;
}
}
}
I've put together a short example on my fork here https://github.com/GallagherCommaJack/may/blob/master/benches/lib.rs
There's an extreme amount of jitter in the performance - from a cold start it'll be quite fast, then suddenly slow down, with speed coming back in bursts of ~100 iterations.
From looking at perf reports I'm guessing this is an issue with not closing out TCP connections properly, but I haven't looked closely enough at the TCP implementations to be sure.
I've also made a separate gist for just the tcp bench - https://gist.github.com/GallagherCommaJack/50f1880b434cacc48eacd19e94ca12af
The caveats mention that one should not use copy
since it can easily blow up the stack size. What's the recommended method for passing bytes from one stream into another?
This line uses the relatively slow serde_json::Value and allocates two BTreeMap nodes and two Strings.
I used the following microbenchmark to compare json!
vs serde_derive as used by the hyper and tokio-minihttp implementations. On my machine:
test bench_derive ... bench: 59 ns/iter (+/- 5)
test bench_json_value ... bench: 254 ns/iter (+/- 2)
This means you are wasting (254 ns/response - 59 ns/response) * 1,148,424 responses/second = 0.224 CPU-seconds per second, which seems like an enormous amount to give up.
#![feature(test)]
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate serde;
extern crate test;
use test::Bencher;
#[bench]
fn bench_derive(b: &mut Bencher) {
#[derive(Serialize)]
struct Message<'a> {
message: &'a str,
}
b.iter(|| {
serde_json::to_vec(&Message { message: "Hello, World!" }).unwrap();
});
}
#[bench]
fn bench_json_value(b: &mut Bencher) {
b.iter(|| {
serde_json::to_vec(&json!({"message": "Hello, World!"})).unwrap();
});
}
Currently may depends on may_queue, which is LGPL, but is itself licensed under MIT/Apache. This is against the terms of the LGPL. Since you're also the author of may_queue, can you replace the license there with MIT/Apache?
Alternately, you could make all of the may libraries LGPL, or another GPL-compatible license.
there are a lot of work need to about the documentation.
Is there any plans for implementing modules for encrypted networking?
Example
go!(|| {
println!("task with default stack size");
});
go!(|| {
println!("task with specific stack size");
},
4096);
go!(|| {
println!("task with specific stack size and name");
},
4096, String::from("task3"));
need to refine IoData,make sure every io object own only one IoData
impl Drop for IoData to del the handle from eventloop
What's the recommended library for http client/server for may? If none exists perhaps this should be top priority to increase adoption?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.