diff --git a/src/libcore/core.rc b/src/libcore/core.rc index 87d0c57f09da..7253711a05f4 100644 --- a/src/libcore/core.rc +++ b/src/libcore/core.rc @@ -41,6 +41,8 @@ export comm, task, future; export extfmt; export tuple; export to_str; +export swappable; +export dvec, dvec_iter; // NDM seems to be necessary for resolve to work export vec_iter, option_iter; @@ -165,7 +167,13 @@ mod option_iter { } mod result; mod to_str; - +mod swappable; +mod dvec; +#[path="iter-trait"] +mod dvec_iter { + #[path = "dvec.rs"] + mod inst; +} // Concurrency mod comm; diff --git a/src/libcore/dvec.rs b/src/libcore/dvec.rs new file mode 100644 index 000000000000..30dbc2a71518 --- /dev/null +++ b/src/libcore/dvec.rs @@ -0,0 +1,237 @@ +// Dynamic Vector +// +// A growable vector that makes use of unique pointers so that the +// result can be sent between tasks and so forth. +// +// Note that recursive use is not permitted. + +import dvec_iter::extensions; +import unsafe::reinterpret_cast; +import ptr::{null, extensions}; + +export dvec; +export from_vec; +export extensions; +export unwrap; + +#[doc = " + +A growable, modifiable vector type that accumulates elements into a +unique vector. + +# Limitations on recursive use + +This class works by swapping the unique vector out of the data +structure whenever it is to be used. Therefore, recursive use is not +permitted. That is, while iterating through a vector, you cannot +access the vector in any other way or else the program will fail. If +you wish, you can use the `swap()` method to gain access to the raw +vector and transform it or use it any way you like. Eventually, we +may permit read-only access during iteration or other use. + +# WARNING + +For maximum performance, this type is implemented using some rather +unsafe code. In particular, this innocent looking `[mut A]` pointer +*may be null!* Therefore, it is important you not reach into the +data structure manually but instead use the provided extensions. + +The reason that I did not use an unsafe pointer in the structure +itself is that I wanted to ensure that the vector would be freed when +the dvec is dropped. The reason that I did not use an `option` +instead of a nullable pointer is that I found experimentally that it +becomes approximately 50% slower. This can probably be improved +through optimization. You can run your own experiments using +`src/test/bench/vec-append.rs`. My own tests found that using null +pointers achieved about 103 million pushes/second. Using an option +type could only produce 47 million pushes/second. + +"] +type dvec = { + + mut data: [mut A] +}; + +#[doc = "Creates a new, empty dvec"] +fn dvec() -> dvec { + {mut data: [mut]} +} + +#[doc = "Creates a new dvec with the contents of a vector"] +fn from_vec(+v: [mut A]) -> dvec { + {mut data: v} +} + +#[doc = "Consumes the vector and returns its contents"] +fn unwrap(-d: dvec) -> [mut A] { + let {data: v} <- d; + ret v; +} + +impl private_methods for dvec { + fn check_not_borrowed() { + unsafe { + let data: *() = unsafe::reinterpret_cast(self.data); + if data.is_null() { + fail "Recursive use of dvec"; + } + } + } + + #[inline(always)] + fn borrow(f: fn(-[mut A]) -> B) -> B { + unsafe { + let mut data = unsafe::reinterpret_cast(null::<()>()); + data <-> self.data; + let data_ptr: *() = unsafe::reinterpret_cast(data); + if data_ptr.is_null() { fail "Recursive use of dvec"; } + ret f(data); + } + } + + #[inline(always)] + fn return(-data: [mut A]) { + unsafe { + self.data <- data; + } + } +} + +// In theory, most everything should work with any A, but in practice +// almost nothing works without the copy bound due to limitations +// around closures. +impl extensions for dvec { + #[doc = " + + Swaps out the current vector and hands it off to a user-provided + function `f`. The function should transform it however is desired + and return a new vector to replace it with. + + "] + fn swap(f: fn(-[mut A]) -> [mut A]) { + self.borrow { |v| self.return(f(v)) } + } + + #[doc = "Returns the number of elements currently in the dvec"] + fn len() -> uint { + self.borrow { |v| + let l = v.len(); + self.return(v); + l + } + } + + #[doc = "Overwrite the current contents"] + fn set(+w: [mut A]) { + self.check_not_borrowed(); + self.data <- w; //FIXME check for recursive use + } +} + +impl extensions for dvec { + #[doc = "Append a single item to the end of the list"] + fn push(t: A) { + self.swap { |v| v += [t]; v } // more efficient than v + [t] + } + + #[doc = "Remove and return the last element"] + fn pop() -> A { + self.borrow { |v| + let result = vec::pop(v); + self.return(v); + result + } + } + + #[doc = " + Append all elements of a vector to the end of the list + + Equivalent to `append_iter()` but potentially more efficient. + "] + fn push_all(ts: [const A]/&) { + self.push_slice(ts, 0u, vec::len(ts)); + } + + #[doc = " + Appends elements from `from_idx` to `to_idx` (exclusive) + "] + fn push_slice(ts: [const A]/&, from_idx: uint, to_idx: uint) { + self.swap { |v| + let new_len = vec::len(v) + to_idx - from_idx; + vec::reserve(v, new_len); + let mut i = from_idx; + while i < to_idx { + v += [ts[i]]; + i += 1u; + } + v + } + } + + //FIXME-- + //#[doc = " + // Append all elements of an iterable. + // + // Failure will occur if the iterable's `each()` method + // attempts to access this vector. + //"] + //fn append_iter>(ts: I) { + // self.dvec.swap { |v| + // alt ts.size_hint() { + // none {} + // some(h) { vec::reserve(v, len(v) + h) } + // } + // + // for ts.each { |t| v = v + [t] }; + // + // v + // } + //} + + #[doc = " + Gets a copy of the current contents. + + See `unwrap()` if you do not wish to copy the contents. + "] + fn get() -> [A] { + self.borrow { |v| + let w = vec::from_mut(copy v); + self.return(v); + w + } + } + + #[doc = "Remove and return the first element"] + fn shift() -> A { + self.borrow { |v| + let mut v = vec::from_mut(v); + let result = vec::shift(v); + self.return(vec::to_mut(v)); + result + } + } + + #[doc = "Copy out an individual element"] + #[inline] + fn [](idx: uint) -> A { + self.get_elt(idx) + } + + #[doc = "Copy out an individual element"] + #[inline] + fn get_elt(idx: uint) -> A { + self.check_not_borrowed(); + ret self.data[idx]; + } + + #[doc = "Overwrites the contents of the element at `idx` with `a`"] + fn set_elt(idx: uint, a: A) { + self.check_not_borrowed(); + self.data[idx] = a; + } + + #[doc = "Overwrites the contents of the element at `idx` with `a`"] + fn grow_set_elt(idx: uint, initval: A, val: A) { + self.swap { |v| vec::grow_set(v, idx, initval, val); v } + } +} \ No newline at end of file diff --git a/src/libcore/iter-trait/dvec.rs b/src/libcore/iter-trait/dvec.rs new file mode 100644 index 000000000000..c68134153cdf --- /dev/null +++ b/src/libcore/iter-trait/dvec.rs @@ -0,0 +1,16 @@ +type IMPL_T = dvec::dvec; + +#[doc = " +Iterates through the current contents. + +Attempts to access this dvec during iteration will fail. +"] +fn EACH(self: IMPL_T, f: fn(A) -> bool) { + import dvec::extensions; + self.swap { |v| vec::each(v, f); v } +} + +fn SIZE_HINT(self: IMPL_T) -> option { + import dvec::extensions; + some(self.len()) +} diff --git a/src/libcore/swappable.rs b/src/libcore/swappable.rs new file mode 100644 index 000000000000..5df9b2c48463 --- /dev/null +++ b/src/libcore/swappable.rs @@ -0,0 +1,98 @@ +export swappable; +export unwrap; +export methods; + +#[doc = " +A value that may be swapped out temporarily while it is being processed +and then replaced. Swappables are most useful when working with unique +values, which often cannot be mutated unless they are stored in the local +stack frame to ensure memory safety. + +The type guarantees the invariant that the value is always \"swapped in\" +except during the execution of the `swap()` and `with()` methods. +"] +type swappable = { + mut o_t: option +}; + +#[doc = "Create a swappable swapped in with a given initial value"] +fn swappable(+t: A) -> swappable { + {mut o_t: some(t)} +} + +#[doc = "Consumes a swappable and returns its contents without copying"] +fn unwrap(-s: swappable) -> A { + let {o_t: o_t} <- s; + option::unwrap(o_t) +} + +impl methods for swappable { + #[doc = " + Overwrites the contents of the swappable + "] + fn set(+a: A) { + self.o_t <- some(a); + } + + #[doc = " + Invokes `f()` with the current value but replaces the + current value when complete. Returns the result of `f()`. + + Attempts to read or access the receiver while `f()` is executing + will fail dynamically. + "] + fn with(f: fn(A) -> B) -> B { + let mut o_u = none; + self.swap { |t| o_u <- some(f(t)); t } + option::unwrap(o_u) + } + + #[doc = " + Invokes `f()` with the current value and then replaces the + current value with the result of `f()`. + + Attempts to read or access the receiver while `f()` is executing + will fail dynamically. + "] + fn swap(f: fn(-A) -> A) { + alt self.o_t { + none { fail "no value present---already swapped?"; } + some(_) {} + } + + let mut o_t = none; + o_t <-> self.o_t; + self.o_t <- some(f(option::unwrap(o_t))); + } + + #[doc = "True if there is a value present in this swappable"] + fn is_present() -> bool { + alt self.o_t { + none {false} + some(_) {true} + } + } + + #[doc = " + Removes the value from the swappable. Any further attempts + to use the swapabble without first invoking `set()` will fail. + "] + fn take() -> A { + alt self.o_t { + none { fail "swapped out"; } + some(_) {} + } + + let mut o_t = none; + option::unwrap(o_t) + } +} + +impl methods for swappable { + #[doc = " + Copies out the contents of the swappable + "] + fn get() -> A { + self.o_t.get() + } +} \ No newline at end of file diff --git a/src/test/bench/vec-append.rs b/src/test/bench/vec-append.rs new file mode 100644 index 000000000000..a84c776f0cc6 --- /dev/null +++ b/src/test/bench/vec-append.rs @@ -0,0 +1,53 @@ +// A raw test of vector appending performance. + +use std; +import dvec::{dvec, extensions}; +import io::writer_util; + +fn collect_raw(num: uint) -> [uint] { + let mut result = []; + uint::range(0u, num) { |i| + result += [i]; + } + ret result; +} + +fn collect_dvec(num: uint) -> [mut uint] { + let result = dvec(); + uint::range(0u, num) { |i| + result.push(i); + } + ret dvec::unwrap(result); +} + +fn main(args: [str]) { + let args = if vec::len(args) <= 1u {["", "100000"]} else {args}; + let max = uint::from_str(args[1]).get(); + let start = std::time::precise_time_s(); + collect_raw(max); + let mid = std::time::precise_time_s(); + collect_dvec(max); + let end = std::time::precise_time_s(); + + let raw = mid - start; + let dvec = end - mid; + + let maxf = max as float; + let rawf = raw as float; + let dvecf = dvec as float; + + io::stdout().write_str(#fmt("Raw : %? seconds\n", raw)); + io::stdout().write_str(#fmt(" : %f op/sec\n", maxf/rawf)); + io::stdout().write_str(#fmt("\n")); + io::stdout().write_str(#fmt("Dvec : %? seconds\n", dvec)); + io::stdout().write_str(#fmt(" : %f op/sec\n", maxf/dvecf)); + io::stdout().write_str(#fmt("\n")); + + if dvec < raw { + io::stdout().write_str(#fmt("Dvec is %f%% faster than raw\n", + (rawf - dvecf) / rawf * 100.0)); + } else { + io::stdout().write_str(#fmt("Raw is %f%% faster than dvec\n", + (dvecf - rawf) / dvecf * 100.0)); + } +} diff --git a/src/test/run-pass/dvec-test.rs b/src/test/run-pass/dvec-test.rs new file mode 100644 index 000000000000..01ae5e73d913 --- /dev/null +++ b/src/test/run-pass/dvec-test.rs @@ -0,0 +1,26 @@ +import dvec::{dvec, extensions}; + +fn main() { + let d = dvec(); + d.push(3); + d.push(4); + assert d.get() == [3, 4]; + d.set([mut 5]); + d.push(6); + d.push(7); + d.push(8); + d.push(9); + d.push(10); + d.push_vec([11, 12, 13]); + + let exp = [5, 6, 7, 8, 9, 10, 11, 12, 13]; + assert d.get() == exp; + assert d.get() == exp; + assert d.len() == exp.len(); + + for d.eachi { |i, e| + assert e == exp[i]; + } + + assert dvec::unwrap(d) == [5, 6, 7, 8, 9, 10, 11, 12, 13]; +} \ No newline at end of file diff --git a/src/test/run-pass/swappable-test.rs b/src/test/run-pass/swappable-test.rs new file mode 100644 index 000000000000..ee3446489017 --- /dev/null +++ b/src/test/run-pass/swappable-test.rs @@ -0,0 +1,13 @@ +import swappable::{swappable, methods}; + +fn main() { + let d = swappable(3); + assert d.get() == 3; + d.set(4); + assert d.get() == 4; + d.swap { |i| i + 1 }; + assert d.get() == 5; + assert d.with { |i| i + 1 } == 6; + assert d.get() == 5; + assert swappable::unwrap(d) == 5; +} \ No newline at end of file