A colony is a C++ template-based unordered data container which provides better performance than any std:: library container when:
While the benchmarks in the section below are a better area to get a feel for the performance benefits, the general speed characteristics are:
As explored further in the benchmarks there are some vector/deque modifications which can outperform during iteration while maintaining pointer validity, but at a cost to usability and memory use. Colony has other advantages including the freeing and recycling of unused memory on-the-fly, the guaranteed stability of pointers/references/iterators to non-erased elements (which makes programming with containers of interrelated data structures faster and much easier), and broad cross-compiler support.
It can be reasonably compared to a "bag" or "bucket-array" styled structure, where each element location can be emptied and skipped over. Unlike a bucket array it does not use keys, iterates faster due to the use of a jump-counting skipfield instead of a boolean field, and frees or recycles unused memory from erased element locations on the fly. As it was initially developed predominantly for game development, colony favours small and large struct/class performance over scalar-type (float, int, etc) performance.
When working on video game engines we are predominantly dealing with
collections of data where:
(Note: These requirements match other areas of software development as well,
but to simplify discussion I will focus on video game development)
To meet these requirements, game developers tend to either (a) develop their own custom containers for given scenario or (b) develop workarounds for the failings of standard containers. These workarounds are many and varied, but the most common are probably:
The first technique has the advantage of fast erasure and simplicity of use, but is comparablty slow to iterate over and never frees memory to the OS. The second technique still has somewhat slow erasure but fast iteration, and also never frees memory to the OS. The first technique disallows insertion on the fly, as this invalidates pointer references. The second can maintain external references upon subsequent insertions, but only if the external object referencing the data knows both the container and the index, which complicates implementation. In addition, insertions will be slow due to the vector's need to reallocate once reaching capacity.
Colony is an attempt to bring a better and more generic solution to this area of work. It has the advantage of fast iteration while maintaining the same extremely fast erasure speed of the boolean technique described above, and without causing pointer invalidation upon either erasure or insertion. It's insertion speed is also faster than a vector's. Memory from erased elements is either reused by subsequent insertions or released to the OS on-the-fly. It achieves these ends by via a number of new approaches: a jump-counting skipfield (instead of a boolean field), a linked chain of increasingly-large memory blocks (instead of a singular memory block or vector of blocks), and a custom stack (plf::stack) for memory location reuse.
More data on the performance characteristics of the colony versus other containers and workarounds can be found in the benchmarks section, but read further below to get a grasp of the mechanics of this container.
plf::colony uses a chained-group memory allocation pattern with a growth factor of 2, (doubly-linked chains of element "groups" containing memory blocks with additional structure metadata, including in this case the jump-counting skipfield). While a growth factor of 2 gives the best performance in the majority of scenarios (which is why the same growth factor is used for the majority of vector implementations), in scenarios where a smaller or more static increase is required, colony's minimum and maximum group sizes can be adjusted to suit. This group-based approach removes the necessity for data reallocation upon insertion, and because data is not reallocated, all references/pointers/iterators to container elements stay valid after insertions to the container.
Due to a std::vector being the most widely-used and commonly-understood of the std:: library containers, we will now contrast the storage mechanisms of colony with that of a std::vector:
The lack of reallocation also explains why insertion into a colony is faster than insertion into a std::vector. Colony has two other internal mechanisms: one is the jump-counting skipfield, utilising an array of 16-bit integers corresponding to every element in the container, which enables the colony to skip over erased elements during iteration. The second is a plf::stack of erased element locations for reuse during subsequent insertion. When an element is erased in a std::vector, the following happens:
When an element is erased in a colony, this happens:
This also explains the typical performance advantage you get when erasing from a colony (variability based on element sizeof and amount of elements) versus erasing from a std::vector.
Upon subsequent insertions to a colony following an erasure, the container will check to see if erased location stack is empty. If it is empty, it inserts to the end of the colony, creating a new group if the old group is full. If not empty, it instead pops an erased element location off the stack and reuses that location for the newly-inserted element. If you erase all the elements in any given group in a colony, the group is removed from the colony's group chain and it's erased element locations removed from the location stack.
plf::colony is under a permissive zlib license. This means: Software is used on 'as-is' basis. It can be modified and used in commercial software. Authors are not liable for any damages arising from its use. The distribution of a modified version of the software is subject to the following restrictions:
Download here (32kb zip file) or
view the repository
The colony library is a simple .h header file, to be included with a #include
command. The package includes the plf::stack .h file, which is used internally
by plf::colony.
In addition if you are interested in benchmarking you can also download the plf benchmark suite (36kb zip
file), which includes plf::nanotimer.
Colony meets the requirements of the C++ Container, AllocatorAwareContainer, and ReversibleContainer concepts.
For the most part the syntax and semantics of colony functions are very similar to all std:: c++ libraries. Formal description is as follows:
template <class T, class Allocator = std::allocator<T>, typename
Skipfield_Type = unsigned short> class colony
T
- the element type. In general T must meet the
requirements of Erasable, CopyAssignable
and CopyConstructible.
However, if only move-insert and/or emplace are utilized to insert elements
into the colony, T is only required to meet the requirements of MoveConstructible and Erasable.
Allocator
- an allocator that is used to acquire memory to
store the elements. The type must meet the requirements of Allocator. The
behavior is undefined if Allocator::value_type
is not the same as
T.
Skipfield_Type
- an unsigned integer type. This type is
used to create the skipfield and the maximum size of element memory blocks is
constrained by it's bit-depth due to the nature of a jump-counting skipfield.
For example, unsigned short
on most platforms is 16-bit and
therefore constrains the size of individual memory blocks to a maximum of 65535
elements. unsigned short
has been found to be the optimal type for
performance based on benchmarking. However there may be some memory-constrained
situations where element block allocations of more than 255 elements at a time
would not be desirable. In these situations, unsigned char
may be
used for the skipfield instead, resulting in some additional memory usage
saving for the skipfield itself. It is unlikely for there to be any
circumstances which benefit from a skipfield bit-depth greater than
unsigned short
. If Skipfield_Type
is not an unsigned
integer type, behaviour is undefined.
#include <iostream>
#include "plf_colony.h"
int main(int argc, char **argv)
{
plf::colony<int> i_colony;
// Insert 100 ints:
for (int i = 0; i != 100; ++i)
{
i_colony.insert(i);
}
// Erase half of them:
for (plf::colony<int>::iterator it = i_colony.begin(); it != i_colony.end(); ++it)
{
it = i_colony.erase(it);
}
// Total the remaining ints:
int total = 0;
for (plf::colony<int>::iterator it = i_colony.begin(); it != i_colony.end(); ++it)
{
total += *it;
}
std::cout << "Total: " << total << std::endl;
std::cin.get();
return 0;
}
#include <iostream>
#include "plf_colony.h"
int main(int argc, char **argv)
{
plf::colony<int> i_colony;
plf::colony<int>::iterator it;
plf::colony<int *> p_colony;
plf::colony<int *>::iterator p_it;
// Insert 100 ints to i_colony and pointers to those ints to p_colony:
for (int i = 0; i != 100; ++i)
{
it = i_colony.insert(i);
p_colony.insert(&(*it));
}
// Erase half of the ints:
for (it = i_colony.begin(); it != i_colony.end(); ++it)
{
it = i_colony.erase(it);
}
// Erase half of the int pointers:
for (p_it = p_colony.begin(); p_it != p_colony.end(); ++p_it)
{
p_it = p_colony.erase(p_it);
}
// Total the remaining ints via the pointer colony (pointers will still be valid even after insertions and erasures):
int total = 0;
for (p_it = p_colony.begin(); p_it != p_colony.end(); ++p_it)
{
total += *(*p_it);
}
std::cout << "Total: " << total << std::endl;
if (total == 2500)
{
std::cout << "Pointers still valid!" << std::endl;
}
std::cin.get();
return 0;
}
| All read-only operations, swap, std::swap | Never |
| clear, change_group_sizes, operator = | Always |
| reserve, shrink_to_fit | Only if capacity is changed |
| erase | Only for the erased element |
| insert, emplace | If an iterator is == end() it may be invalidated by a subsequent insert/emplace in some cases. |
| Member type | Definition |
value_type |
T |
allocator_type |
Allocator |
size_type |
Allocator::size_type (pre-c++11) |
difference_type |
Allocator::difference_type (pre-c++11) |
reference |
Allocator::reference (pre-c++11) |
const_reference |
Allocator::const_reference (pre-c++11) |
pointer |
Allocator::pointer (pre-c++11) |
const_pointer |
Allocator::const_pointer (pre-c++11) |
iterator |
BidirectionalIterator |
const_iterator |
Constant BidirectionalIterator |
reverse_iterator |
BidirectionalIterator |
const_reverse_iterator |
Constant BidirectionalIterator |
| default | explicit colony(const allocator_type &alloc =
allocator_type()) |
| fill | explicit colony(const size_type n, const unsigned short
min_group_size = 8, const unsigned short max_group_size = USHRT_MAX,
const allocator_type &alloc = allocator_type()) |
| range | template<typename InputIterator> colony(const
InputIterator &first, const InputIterator &last, const unsigned
short min_group_size = 8, const unsigned short max_group_size =
USHRT_MAX, const allocator_type &alloc = allocator_type()) |
| copy | colony(const colony &source) |
| move | colony(colony &&source) noexcept (C++11
and upwards) |
| initializer list | colony(const std::initializer_list<value_type>
&element_list, const unsigned short min_group_size = 8, const
unsigned short max_group_size = USHRT_MAX, const allocator_type
&alloc = allocator_type()) |
colony<T> a_colony
Default constructor - default minimum group size is 8, default maximum
group size is USHRT_MAX (typically 65535). You cannot set the group sizes
from the constructor in this scenario, but you can call the
change_group_sizes() member function after construction has occurred.
Example: plf::colony<int>
int_colony;
colony<T, the_allocator<T> > a_colony(const
allocator_type &alloc = allocator_type())
Default constructor, but using a custom memory allocator eg. something
other than std::allocator.
Example: plf::colony<int,
tbb::allocator<int> > int_colony;
Example2:
// Using an instance of an allocator as well as
it's type
tbb::allocator<int> alloc_instance;
plf::colony<int, tbb::allocator<int> >
int_colony(alloc_instance);
colony<T> colony(const size_type n, const unsigned short
min_group_size = 8, const unsigned short max_group_size = USHRT_MAX)
Fill constructor with value_type unspecified, so the value_type's
default constructor is used. n
specifies the number of
elements to create upon construction. If n
is larger than
min_group_size
, the size of the groups created will either be
n
and max_group_size
, depending on which is
smaller. min_group_size
(ie. the smallest possible number of
elements which can be stored in a colony group) can be defined, as can the
max_group_size
. Setting the group sizes can be a performance
advantage if you know in advance roughly how many objects are likely to be
stored in your colony long-term - or at least the rough scale of storage.
If that case, using this can stop many small initial groups being
allocated.
Example: plf::colony<int>
int_colony(62);
colony<T> colony(const
std::initializer_list<value_type> &element_list, const unsigned
short min_group_size = 8, const unsigned short max_group_size =
USHRT_MAX)
Using an initialiser list to insert into the colony upon
construction.
Example: std::initializer_list<int>
&el = {3, 5, 2, 1000};
plf::colony<int> int_colony(el, 64, 512);
colony<T> a_colony(const colony &source)
Copy all contents from source colony, removes any empty (erased) element
locations in the process. Size of groups created is either the total size
of the source colony, or the maximum group size of the source colony,
whichever is the smaller.
Example: plf::colony<int>
int_colony_2(int_colony_1);
colony<T> a_colony(colony &&source)
Move all contents from source colony, does not remove any erased element
locations or alter any of the source group sizes. Source colony is now void
of contents and can be safely destructed.
Example: plf::colony<int> int_colony_1(50,
5, 512, 512); // Create colony with min and max group sizes set at 512
elements. Fill with 50 instances of int = 5.
plf::colony<int> int_colony_2(std::move(int_colony_1)); // Move all
data to int_colony_2. All of the above characteristics are now applied to
int_colony2.
All iterators are bidirectional but also provide >, <, >= and <= for convenience (for example, in for loops). Functions for iterator, reverse_iterator, const_iterator and const_reverse_iterator follow:
operator *
operator ->
operator ++
operator --
operator =
operator ==
operator !=
operator <
operator >
operator <=
operator >=
base() (reverse_iterator and const_reverse_iterator only)
All operators have O(1) amortised time-complexity. Originally there were +=, -=, + and - operators, however the time complexity of these varied from O(n) to O(1) depending on the underlying state of the colony, averaging in at O(log n). As such they were not includable in the iterator functions (as per C++ standards). These have been transplanted to colony's advance(), next(), prev() and distance() member functions. Greater-than/lesser-than operator usage indicates whether an iterator is higher/lower in position compared to another iterator in the same colony (ie. closer to the end/beginning of the colony).
| single element | iterator insert (const value_type &val) |
| fill | iterator insert (const size_type n, const value_type
&val) |
| range | template <class InputIterator> iterator insert (const
InputIterator &first, const InputIterator &last) |
| move | iterator insert (value_type&& val) (C++11 and upwards) |
| initializer list | iterator insert (const std::initializer_list<value_type>
&il) |
iterator insert(const value_type &element)
Inserts the element supplied to the colony, using the object's copy-constructor. Will insert the element into a previously erased element slot if one exists, otherwise will insert to back of colony. Returns iterator to location of inserted element. Example:
plf::colony<unsigned int> i_colony;
i_colony.insert(23);
iterator insert (const size_type n, const value_type
&val)
Inserts n
copies of val
into the colony. Will
insert the element into a previously erased element slot if one exists,
otherwise will insert to back of colony. Returns iterator to location of
first inserted element. Example:
plf::colony<unsigned int> i_colony;
i_colony.insert(10, 3);
template <class InputIterator> iterator insert (const
InputIterator &first, const InputIterator &last)
Inserts a series of value_type
elements from an external
source into a colony holding the same value_type
(eg. int,
float, a particular class, etcetera). Stops inserting once it reaches
last
. Example:
// Insert all contents of colony2 into
colony1:
colony1.insert(colony2.begin(), colony2.end());
iterator insert(value_type &&element) C++11 and
upwards
Moves the element supplied to the colony, using the object's move-constructor. Will insert the element in a previously erased element slot if one exists, otherwise will insert to back of colony. Returns iterator to location of inserted element. Example:
std::string string1 = "Some text";
plf::colony<std::string> data_colony;
data_colony.insert(std::move(string1));
iterator insert (const std::initializer_list<value_type>
&il)
Moves the element supplied to the colony, using the object's move-constructor. Will insert the element in a previously erased element slot if one exists, otherwise will insert to back of colony. Returns iterator to location of inserted element. Example:
std::initializer_list<int> some_ints =
{4, 3, 2, 5};
plf::colony<int> i_colony;
i_colony.insert(some_ints);
| single element via iterator | iterator erase(const iterator &it) |
| single element via pointer | int erase(const value_type *p) |
| range | void erase(const iterator &first, const iterator
&last) |
iterator erase(const iterator &it)
Removes the element pointed to by the supplied iterator, from the colony. Returns an iterator pointing to the next non-erased element in the colony (or to end() if no more elements are available). This must return an iterator because if a colony group becomes entirely empty, it will be removed from the colony, invalidating the existing iterator. Attempting to erase a previously-erased element results in undefined behaviour (this is checked for via an assert in debug mode). Example:
plf::colony<unsigned int>
data_colony(50);
plf::colony<unsigned int>::iterator an_iterator;
an_iterator = data_colony.insert(23);
an_iterator = data_colony.erase(an_iterator);
int erase(const value_type *p)
This enables erasure via a colony element's pointer, rather than it's iterator. This is expected to be useful in the use-case where external containers are storing pointers to colony elements instead of iterators (as iterators have 3 times the size of an element pointer) and the program wants to erase the element being pointed to. This method is about 20% slower on average than iterator-based erasure as a lookup has to be performed to find the colony element's group - but still faster than all std:: container erasure times except std::list, which it is roughly equal to. Returns non-zero if element pointer was not found in any colony group, otherwise returns zero on success. Attempting to erase a previously-erased element results in undefined behaviour (this is checked for via an assert in debug mode). Example:
plf::colony<a_struct> data_colony;
plf::colony<a_struct>::iterator an_iterator;
a_struct struct_instance;
an_iterator = data_colony.insert(struct_instance);
a_struct *struct_pointer = &(*an_iterator);
int success = data_colony.erase(struct_pointer);
if (success) std::cout << "Erasure successful" <<
std::endl;
void erase(const iterator &first, const iterator
&last)
Erases all contents of a given colony from first
to the
element before the last
iterator. Example:
plf::colony<int> iterator1 =
colony1.begin();
colony1.advance(iterator1, 10);
plf::colony<int> iterator2 = colony1.begin();
colony1.advance(iterator2, 20);
colony1.erase(iterator1, iterator2);
iterator emplace(Arguments ...parameters) C++11 and
upwards
Constructs new element directly within colony. Will insert the element in a previously erased element slot if one exists, otherwise will insert to back of colony. Returns iterator to location of inserted element. "...parameters" are whatever parameters are required by the object's constructor. Example:
class simple_class
{
private:
int number;
public:
simple_class(int a_number): number (a_number) {};
};
plf::colony<simple_class> simple_classes;
simple_classes.emplace(45);
bool empty()
Returns a boolean indicating whether the colony is currently empty of
elements.
Example: if (object_colony.empty())
return;
size_type size()
Returns total number of elements currently stored in container.
Example: std::cout << i_colony.size()
<< std::endl;
size_type max_size()
Returns the maximum number of elements that the allocator can store in
the container. This is an approximation as it does attempt to measure the
memory overhead of the container's internal memory structures. It is not
possible to measure the latter because a copy operation may change the
number of groups utilized for the same amount of elements, if the maximum
or minimum group sizes are different in the source container.
Example: std::cout << i_colony.max_size()
<< std::endl;
size_type capacity()
Returns total number of elements currently able to be stored in
container without expansion.
Example: std::cout << i_colony.capacity()
<< std::endl;
void shrink_to_fit()
Reduces container capacity to the amount necessary to store all
currently stored elements. If the total number of elements is larger than
the maximum group size, the resultant capacity will be equal to
((total_elements / max_group_size) + 1) * max_group_size
(rounding down at division). Invalidates all pointers, iterators and
references to elements within the container.
Example: i_colony.shrink_to_fit();
void reserve(unsigned short reserve_amount)
Preallocates memory space sufficient to store the number of elements
indicated by reserve_amount
. The maximum size for this number
is limited to the maximum group size of the colony and will be truncated if
necessary. The default maximum group size is 65535 on the majority of
platforms.
Example: i_colony.reserve(15);
void clear()
Empties the colony and removes all elements and groups.
Example: object_colony.clear();
void change_group_sizes(const unsigned short min_group_size, const
unsigned short max_group_size)
Changes the minimum and maximum internal group sizes, in terms of number
of elements stored per group. If the colony is not empty, all elements will
be moved into new groups and all pointers/iterators/references
invalidated.
Example: object_colony.change_group_sizes(1000,
10000);
void change_minimum_group_size(const unsigned short
min_group_size)
Changes the minimum internal group size only, in terms of minimum number
of elements stored per group. If the colony is not empty, all elements will
be moved into new groups and all pointers/iterators/references
invalidated.
Example: object_colony.change_minimum_group_size(100);
void change_maximum_group_size(const unsigned short
min_group_size)
Changes the maximum internal group size only, in terms of maximum number
of elements stored per group. If the colony is not empty, all elements will
be moved into new groups and all pointers/iterators/references
invalidated.
Example: object_colony.change_maximum_group_size(1000);
void swap(colony &source)
Swaps the colony's contents with that of source
.
Example: object_colony.swap(other_colony);
friend void swap(colony &A, source &B)
External friend function, swaps the colony A's contents with that of
colony B (assumes both stacks have same element type).
Example: swap(object_colony,
other_colony);
colony & operator = (const colony &source)
Copy the elements from another colony to this colony, clearing this
colony of existing elements first.
Example: // Manually swap data_colony1 and
data_colony2 in C++03
data_colony3 = data_colony1;
data_colony1 = data_colony2;
data_colony2 = data_colony3;
colony & operator = (const colony &&source) C++11
only
Move the elements from another colony to this colony, clearing this
colony of existing elements first. Source colony becomes invalid but can be
safely destructed without undefined behaviour.
Example: // Manually swap data_colony1 and
data_colony2 in C++11
data_colony3 = std::move(data_colony1);
data_colony1 = std::move(data_colony2);
data_colony2 = std::move(data_colony3);
bool operator == (const colony &source)
Compare contents of another colony to this colony. Returns a boolean as
to whether they are equal.
Example: if (object_colony == object_colony2)
return;
bool operator != (const colony &source)
Compare contents of another colony to this colony. Returns a boolean as
to whether they are not equal.
Example: if (object_colony != object_colony2)
return;
iterator begin(), iterator end(), const_iterator cbegin(),
const_iterator cend()
Return iterators pointing to, respectively, the first element of the colony and the element one-past the end of the colony (as per standard STL guidelines).
reverse_iterator rbegin(), reverse_iterator rend(),
const_reverse_iterator cbegin(), const_reverse_iterator cend()
Return reverse iterators pointing to, respectively, the last element of the colony and the element one-before the first element of the colony (as per standard STL guidelines).
template <iterator_type> void advance(iterator_type iterator,
distance_type distance)
Increments/decrements the iterator supplied by the positive or negative
amount indicated by distance. Speed of incrementation will almost
always be faster than using the ++ operator on the iterator for increments
greater than 1. In some cases it may approximate O(1). The iterator_type
can be an iterator, const_iterator, reverse_iterator or
const_reverse_iterator.
Example: colony<int>::iterator it =
i_colony.begin();
i_colony.advance(it, 20);
template <iterator_type> iterator_type next(const
iterator_type &iterator, distance_type distance)
Creates a copy of the iterator supplied, then increments/decrements this
iterator by the positive or negative amount indicated by
distance.
Example: colony<int>::iterator it =
i_colony.next(i_colony.begin(), 20);
template <iterator_type> iterator_type prev(const
iterator_type &iterator, distance_type distance)
Creates a copy of the iterator supplied, then decrements/increments this
iterator by the positive or negative amount indicated by
distance.
Example: colony<int>::iterator it2 =
i_colony.prev(i_colony.end(), 20);
template <iterator_type> difference_type distance(const
iterator_type &first, const iterator_type &last)
Measures the distance between two iterators, returning the result, which
will be negative if the second iterator supplied is before the first
iterator supplied in terms of it's location in the colony.
Example: colony<int>::iterator it =
i_colony.next(i_colony.begin(), 20);
colony<int>::iterator it2 = i_colony.prev(i_colony.end(), 20);
std::cout "Distance: " i_colony.distance(it, it2) std::endl;
Last updated 16-04-2016 v3.04
The test setup is an E8500 on an Intel motherboard, 8GB ram, running GCC 5.1 x64 as compiler. Build settings are "-O2;-march=native;-std=c++11;-fomit-frame-pointer". Results for Visual Studio 2013 can be found here. Tests are based on a sliding scale of number of runs vs number of elements, so a test with only 10 elements in a container may average 100000 runs, whereas a test with 100000 elements may only average 10 runs. This tends to give adequate results without overly lengthening test times. I have not included results involving 'reserve()' functions as the differences to overall insertion performance were not adequate. The source code can be found in the downloads section above.
Insertion: is into empty containers (with the exception of one
clearly-labeled re-insertion test toward the end), entering single elements at
a time. This matches the use case of colony, where insertion on-the-fly is
expected.
Erasure: initially takes place in an iterative fashion for the raw
tests, erasing elements at random as we iterate through the container. This
does not fit best use case for std::deque or std::vector, which would usually
take the form of a remove_if pattern, but that pattern makes no performance
difference to colony, and the raw tests are primarily looking for good
candidates to compare versus colony. In the comparative and modification tests
vector/deque comparisons with remove_if erasures are included.
Iteration: is straightforward iteration from the start to end of any
containers. Typically there are more runs for iteration than the other tests
due to them being a much quicker procedure, so more data delivers a more stable
average.
Before we begin measuring colony against equivalent containers (or modified containers) ie. ones which do not invalidate pointers on erasure or insertion, we need to identify which containers are good candidates for comparison based on raw testing without regard to pointer/iteration invalidation. With that in mind the following tests compares colony against the typical standard library containers, and boost::segmented_tree, a current boost candidate.
The first test measures time to insert N elements into a given container, the second measures the time taken to erase 25% of those same elements from the container, and the third test measures iteration performance after the second test has taken place. Both linear and logarithmic views of each benchmark are provided in order to better show the performance of lower element amounts.
Note: because plf::colony is an unordered container and subsequently we are not concerned about insert order, push_front has been used with std::list instead of push_back, in order to provide a fair performance comparison.
A predictable pattern for all but the large struct test is shown for
insertion:
std::deque dominates insertion, with plf::colony equal after about 100
elements, but then for large structs it's performance completely eclipses
std::deque. std::vector is nearly on a par with std::deque for very small
element types with element numbers greater than a thousand, but becomes worse
and worse the larger the size of the stored type is, and the fewer number of
stored elements there are.
segmented_tree is generally slightly worse than plf::colony for small scalar
types but is significantly worse for larger types - structs and classes.
std::list, std::map and std::multiset all perform poorly by contrast, but with
large structs the situation is almost reversed, with std::list and std::map
outperforming all other contendors except for plf::colony.
Overall, plf::colony and std::deque dominate, with segmented_tree taking third place and std::list/std::map getting honourable mentions for large struct performance.
Here we forward-iterate over each container and erase 25% of all elements randomly. Although typical tests for erasure speed tend to simply select element indexes within a container randomly and erase those elements in a non-linear sequence, this does not match real-world usage for game development. Typically in game development data is processed sequentially for performance reasons, and any elements that need to be removed/deactivated are done so during this iteration, or subsequently in a post-iteration cull depending on implementation. Similarly, reverse-iteration (which would enhance vector erasure performance for example) is not common for cache performance reasons, but since it might be used occasionally I have given some role to it: If (due to the variability of random number generators) 25% of all elements have no been erased by the end of the container, the test will reverse-iterate through the container and randomly erase the remaining necessary number of elements until that 25% has been reached:
Across all types, plf::colony dominates performance, with std::list coming close behind and segmented_tree in third place. std::deque and std::vector have predictably terrible performance, as much as 100000x worse than plf::colony and std::list for large numbers of large types.
Since data is typically iterated across far more than it is erased or inserted, iteration speed is for many programs more important than erase or insertion performance, despite the fact that it is always factors of ten faster than either of those two.
For under 1000 elements, std::list is about on par with both std::deque and std::vector, both of which dominate these tests, with std::vector taking 1st place. However the number of elements before this effect occurs on std::list decreases according to how large the stored type is, suggesting that performance in this case is due to some effect of the cpu cache or implementation. Querying the GCC mailing list about this resulted in the following response, which I believe to be accurate due to the correlation between std::list iteration performance and type size: "I suspect that for working sets which fit into the first-level cache of the CPU, the simpler iterators for std::list are faster than std::deque because the additional memory accesses in std::list are cheaper than the fairly complicated iterator implementation in std::deque". What this suggests is that for typical programs, where more than one data set is competing for space in the L1 or L2 caches, std::list performance will not follow the pattern above and generally will be poor.
plf::colony performs worse than segmented_tree for scalar types, except for char, where segmented_tree fairs very poorly, and it outperforms it for anything larger than a scalar ie. structs and classes.
From the above data we can see that std::list is a good contendor against plf::colony, but only in the following scenario - where (a) the amount of data in the container is small enough to fit entirely into the cache and (b) where that data set is the only data set being operated on by the program in question, and in fact the computer as a whole. That being the case, std::list is not a general contendor, but may have a place in specialised scenarios, though probably certainly not in game development. That aside, it has the advantage of not requiring modification to avoid invalidation of pointers on insertion or erasure.
segmented_tree overall trails behind plf::colony and invalidates pointers/iterators on both insertion and erasure, making it a non-contendor for the areas where we might choose to use a colony ie. where pointer/iterator stability is required and ordered insertion is not. std::deque is a contendor, having strong insertion performance and excellent iteration performance but poor erasure performance - however both std::deque invalidates pointers on erasure, meaning it requires modification to be used. std::vector is a weaker contendor, having weak insertion performance and extremely poor erasure performance, however it's iteration performance is always the best, even if std::deque is very close behind. std::vector invalidates pointers on both insertion and erasure, meaning it will require a different modification stragety to std::deque.
So to reiterate, colony is designed for scenarios where we require good performance while guarantee'ing pointer stability for outside elements referring to elements within the container, and where ordered insertion is unimportant. This describes the majority of situations when building game engines, and possibly in other highly compartmentalised and modular environments. The only other container, based on the raw performance tests, which approaches this goal without modification is std::list, unfortunately as noted this is probably only useful for a few higly specialised scenarios due to it's iteration performance being entirely dependent on fitting all data in the CPU cache. The other two containers which may approach usability via modification are std::deque and std::vector.
Because std::deque does not invalidate pointers upon insertion to the back or front of the container, we don't have to worry about pointers being invalidated during non-ordered insertion, meaning we can use a specific modification which I'lll call a pointer-to-deque deque, or pointer_deque for short. In this scenario we take our deque of elements, and construct a secondary deque containing pointers to each element in the first deque. When we erase, we only erase from the pointer deque, and when we iterate, we iterate over the pointer deque and access only those elements pointed to by the pointer deque. In doing so we reduce erase times for larger-than-scalar types, as it is computationally cheaper to reallocate pointers than larger structs. We also avoid reallocation during erasure for the element deque, meaning pointers and references to elements within the deque stay valid.
We cannot employ the same tactic with std::vector because of the reallocation during insertion regardless of insertion position. What we can do however is to employ a similar tactic using indexes instead of pointers, which we'll call an indexed_vector. Since indexes stay valid regardless of whether the element vector reallocates, index access to the element vector will still work after insertion. Since we never erase from the element vector, only the index vector, the indexes also stay valid during erasure. Index access involves an additional step computationally to reach the desired element, so is slightly slower during iteration than a pointer vector, but has the same advantages during erasure. In addition outside data refering to elements within the indexed_vector must use indexes instead of pointers to refer to the elements. This complicates implementation as the outside data source must know which container it is indexing, whereas a pointer approach can ignore this and simply point to an element.
We will also compare the same two container modifications above using a remove_if erasure pattern, adding an additional boolean field to indicate erasure to the original stored struct type, and utilizing two passes - the first to randomly flag elements as being ready for erasure via the boolean field, the second using the remove_if pattern. This matches some use cases in game engines where erasures are 'saved up' until the end of a given frame, then processes. A third modified approach, which we'll call a deque_bool, is a very common approach in a lot of game engines - a bool or similar type is added to the original struct or class, and this type is tested against to see whether or not the object is 'active' (true) - if inactive (false), it is skipped over. This is somewhat similar to what a colony does, but without the separate jump-counting skipfield which increases iteration speed and removes the branching decisions. Again, for the purposes of this test this is not possible with a vector of elements, as insertion would cause invalidation of pointers to the elements.
Since neither the indexed_vector nor the pointer_deque will have erasure time benefits for small scalar types, and because game development is predominantly concerned with storage of larger-than-scalar types, we will only test using small structs, for simplicity's sake. In addition, we will test 4 levels of erasure: 0% of all elements, 25% of all elements, 50% of all elements, and 75% of all elements. And finally, we will test the performance of the 75%-erased containers after re-inserting 25% of all elements back into each container, to compare how the different models perform after reinsertion.
Here a deque_bool and plf::colony are more-or-less on a par with each other, with both pointer_deque modifications slightly behind and std::list and indexed_vector modifications performing poorly by comparison.
Here the gap consistently widens between the candidates as erasure percentage increases, with deque_bool, plf::colony and std::list being very close to each other in 1st, 2nd and 3rd place respectively. pointer_deque and indexed_vector have much worse performance once the number of elements climbs above 200, 1 factor of ten above the other candidates at 2000 elements and almost 3 factors of ten above the other candidates at 100000 elements. pointer_deque_remove_if and indexed_vector_remove_if perform mostly the same as colony, eclipsing colony slightly for large amounts of erasures.
Here std::list is more-or-less equal to all four indexed_vector and pointer_deque modifications for under 500 elements, then just as in the previous tests, once less of the data fits in the cache entirely it's performance diminishes rapidly. plf::colony and deque_bool trail behind the other contendors.
Now we begin to see the effect of the jump-counting skipfield. std::list does the same thing as before, but with only a quarter of the elements erased, deque_bool's performance drops substantially and becomes worse than std::list. plf::colony however shows only slightly worse results than pointer_deque and indexed_vector after ~500 total elements.
At this point the poor iteration performance of deque_bool becomes much more apparent.
At this point we can see that deque_bool overall performs worse than any other contendor. Other than that it is a continuation of previously established trends.
Summary: The overall pattern shows that pointer_deque and indexed_vector modifications dominate performance with plf::colony quite close behind, std::list performing typically and deque_bool being entirely inadequate once erasures have occurred. The additional boolean field involved in the erasure_if modifications does not appear to have an effect on performance.
At this point we take the containers after the last test, at 75% erasure, and re-insert 25% of the original number of elements, and subsequently test iteration performance to see how reinsertion affects each container.
While plf::colony closes the gap with pointer_deque and indexed_vector modifications, again to become roughly similar to the post-50%-erasure iteration test, deque_bool grows worse again. This is due to the element location recycling that is utilized in plf::colony, as opposed to deque_bool which has no such mechanism.
In this test we take a container, and iterate over it for 3600 cycles, randomly erasing approximately 1 element every 120 cycles, and randomly inserting 1 element every 120 cycles. These numbers do not correspond to the number of elements in the container, in other words, the same number of elements gets removed/inserted for a 10-element container as it does for a 80000-element container. By doing so we simulate a seldom-modified container (2 modifications every 2 seconds) being used by a program for 1 minute where each cycle represents a frame, and where there are 60 frames per second. By doing so we play off the different performance aspects of each container to approximate an overall conclusion. Because of std::list's limitation as per the CPU cache and competing with other potential data as mentioned earlier, and the unlikelihood of real-world usability as a result, we've discluded it from the tests.
We can see pointer_deque winning out here, followed closely by plf::colony and indexed_vector tailing far behind. Both the remove_if modifications fall in the middle, with the overhead of the additional pass obviously forming a performance detriment for pointer_deque, and indexed_vector performing better than indexed_vector_remove_if until around 700 elements.
The same as the previous test, except in this test we perform 2 erasures at random every cycle as well as 2 insertions ie. 4 modifications per frame.
Here the results are much the same but with the increased number of modifications per frame, we see plf::colony starting to edge out pointer_deque in terms of overall performance.
In this test we change the pattern, making the number of elements removed/inserted a percentage of the overall size of the container. In this case we randomly erase and insert around 10% of the container size per frame.
As could be expected, with the much higher number of erasures the standard-erasure pointer_deque and indexed_vector fall far behind after approximately 200 elements (though effects are still noticable at 100 elements). The remove_if variants perform much better, but colony overall dominates performance.
We can legitimately say that if there is no competition for the CPU cache ie. if the container in question is the only container being iterated over by your program, then provided that your amount of data is small and will fit entirely within the CPU cache, std::list is probably the best solution where we (a) need adequate performance and (b) cannot have pointer invalidation and (c) don't require ordered insertion. However this is a very small, unlikely and specialised scenario - in the bulk of scenarios std::list should not be used where pointer stability is required.
Using a bool or similar type to indicate erasure in a container (as represented in this case by deque_bool) is always a poor choice by comparison with the other options available, which becomes much more apparent as more and more erasures occur, and this approach should not be used. Nor does separating the skipfield into a separate block of memory repair the situation, as without the jump-counting skipfield, runs of erasures tend to slow iteration speed. To see a graph of the relative performance of plf::colony using a boolean field instead of a jump-counting skipfield, vs a regular colony, a deque_bool and a deque using, as an alternative, a boolean field separate to the data itself, click here.
pointer_deque and indexed_vector approaches both have strengths in terms of iteration performance but their erasure performances are poor for anything greater than 200 elements, so these two facets have to be traded off against each other to determine overall performance. Their remove_if variants are of course faster for higher numbers of erasures, but slower for small numbers of erasures, so there's an inflexibility to either approach if one wants optimal performance. What the use-case testing shows is that once this and the insertion times are taken into account, the differences between these approaches and plf::colony are minimal for lowered numbers of container insertions/erasures, with pointer_deque having a slight lead in the case of an efficient implementation (which GCC's implementation definitely is). Where high numbers of erasures and insertions are involved, we can see plf::colony has superior overall performance. In addition, while std::deque and std::vector's erasure performance varies significantly based on the size of data and where the element being erased is within that data, plf::colony's is predictable and much faster, making it safer for low-latency environments.
plf::colony has an additional advantage which indexed_vector and pointer_deque do not have - it reuses and releases erased element memory space. When you erase from a indexed_vector/pointer_deque they don't release the memory space to the OS, nor do they reuse the memory space for subsequent insertions - meaning that with a high level of modification both containers are perpetually expanding and never shrinking. Because gaming often occurs on platforms with significant memory constraints this can be less-than-desirable. And although one can implement a stack-based solution for recycling erased element locations for either of these approaches based on a similar technique to what is done in plf::colony, any reinsertions would come at a cost, as adding the reference back into the vector of element indexes, or deque of element pointers, would require an ordered insertion operation which results in reallocation of subsequent pointers/indexes, meaning the insertion cost becomes higher yet again.
For these reasons the overall recommendation is to use plf::colony for all situations where (a) memory space is at a premium, (b) ordered insertion doesn't matter, (c) maintaining valid pointers to container elements is desirable and (d) insertions/erasures are occurring during operation. If erasures and reinsertions are infrequent and memory is not at a premium then you will be better off with a pointer_deque or indexed_vector approach.
plf::colony:
Advantages: Fastest insertion and erase performance
except for deque_bool. Iteration performance very close to pointer_deque and
indexed_vector. Recycles or releases erased element memory locations. Best
overall performance where the ratio of insertions/erasures to iteration is
high. Insertion, erasure and iteration are all O(1) operations.
Disadvantages: Could be slower overall than
pointer_deque or indexed_vector approaches for situations where memory
expansion is not an issue, or where erasure and reinsertion are
infrequent.
pointer_deque:
Advantages: Best overall performance where the ratio
of insertions/erasures to iterations is low. A remove_if approach to
erasure can improve erasure times on large numbers of per-frame
erasures.
Disadvantages: std::deque implementation structure is
compiler-dependent, so the above advantage will not apply generically
across-compilers. Will not release erased element locations to OS, or reuse
them. Slower erasure performance, so if erasure is very frequent, may be very
slow. Erasure time is less predictable and a detriment to latency-critical
programming.
indexed_vector:
Advantages: Almost as fast as pointer_deque
approach. Implementations do not vary so much from compiler to compiler,
meaning that this approach is more likely to yield benefits generically
compared to deque implementations.
Disadvantages: Slower than pointer_deque approach, at
least under GCC. Approach requires any outside structures referring to it's
elements to use indexes instead of pointers, which complicates implementation,
as the structure must also know the container it is referring to. Will not
release erased element locations to OS, or reuse them. Slower erasure
performance than pointer_deque under GCC, so if erasure is very frequent, may
be extremely slow. A remove_if erasure pattern significantly improves
performance for large numbers of erasures but performs worse with low numbers,
and overall is worse than plf::colony performance. This erasure cost may be
reduced by using smaller datatypes for indexes, for example an unsigned short
when it is known that the number of elements will not exceed 65535 - hence
reducing the amount of data needing to be reallocated upon erasures. Erasure
time is less predictable and a detriment to latency-critical
programming.
A combination of a linked-list of increasingly-large memory blocks with metadata, combined with a jump-counting skipfield, combined with an erased-element memory-location stack for later reinsertions, resulting in a std::-styled C++ template data container with positive performance characteristics while maintaining pointer stability to container elements.
It is most worthwhile, for performance reasons, in situations where the order of container elements is not important, and:
Under these circumstances a colony will generally out-perform other std:: containers. In addition, because it never invalidates pointer references to container elements (except when the element being pointed to has been previously erased) it can make many programming tasks involving inter-relating structures in an object-oriented or modular environment much faster, and can be considered in those circumstances.
Some ideal situations to use a colony: quadtree, persistent octtree, general game entities or destructible-objects in a video game, anywhere where objects are being created and destroyed continuously. Also, anywhere where a vector of pointers to dynamically-allocated objects or a std::list would typically end up being used in order to preserve object references.
Insertion: O(1) amortized
Erase: O(1) amortized
std::find: O(n)
Iterator operations:
++ and -- : O(1) amortized
begin()/end(): O(1)
advance/next/prev: between O(1) and O(n), depending on current location,
end location and state of colony. Average O(log N).
A deque is fairly dissimilar to a colony. A deque is a double-ended queue, which requires a bit of a different internal framework. It typically uses a vector of memory blocks, whereas a colony uses a linked list - essentially. They have no comparable performance characteristics except for insertion (on a decent deque implementation). Deque element erasure performance varies quite wildly depending on the compiler compared to std::vector, and it does not free unused memory blocks to the OS once they are empty, unlike a colony. In addition a deque invalidates references to subsequent container elements when erasing elements, which a colony does not.
Unlike a std::vector, a colony can be read from and written to at the same time, however it cannot be iterated over and written to at the same time. If we look at a std::vector's threadsafe matrix, to see which basic operations can occur at the same time, it reads as follows (please note push_back() is the same as insertion in this regard):
| std::vector | Insertion | Erasure | Iteration | Read |
| Insertion | No | No | No | No |
| Erasure | No | No | No | No |
| Iteration | No | No | Yes | Yes |
| Read | No | No | Yes | Yes |
In other words, multiple reads and iterations over iterators can happen simultaneously, but the invalidation caused by insertion/push_back and erasure means those operations cannot occur at the same time as anything else.
Colony on the other hand does not (mostly) invalidate pointers/iterators to elements during insertion and erasure (iterators == end() may be invalidated by subsequent insertions, and iterators to elements which are erased will become invalid), resulting in the following matrix:
| plf::colony | Insertion | Erasure | Iteration | Read |
| Insertion | No | No | Mostly | Yes |
| Erasure | No | No | No | Mostly |
| Iteration | Mostly | No | Yes | Yes |
| Read | Yes | Mostly | Yes | Yes |
In other words, reads may occur at the same time as insertions and erasures (provided that the element being erased is not the element being read), multiple reads and iterations may occur at the same time, but iterations may not occur at the same time as an erasure or insertion, as either of these may change the state of the skipfield which's being iterated over. Erasures will not invalidate iterators unless the iterator points to the erased element.
So, colony is inherently more threadsafe than a std::vector, but still has some areas which would require mutexes or atomics to navigate in a multithreaded environment.
Only a few:
In the special case where many, many elements are being continually
erased/inserted in realtime, you might want to experiment with limiting the
size of your internal memory groups in the constructor. The form of this is
as follows:
plf::vector<object> a_vector;
a_vector.change_group_sizes(500, 5000);
where the first number is the minimum size of the internal memory groups
and the second is the maximum size. Note these can be the same size,
resulting in an unchanging group size for the lifetime of the colony
(unless change_group_sizes
is called again or operator = is
called).
One reason to do this is that it is slightly slower to pop an element
location off the internal memory position recycling stack, than it is to
insert a new element to the end of the colony (the default behaviour when
there are no previously-erased elements). If there are any erased elements
in the colony, the colony will recycle those memory locations, unless the
entire group is empty, at which point it is freed to memory. So if a group
size is large and many, many erasures occur but the group is not completely
emptied, (a) the number of erased element locations in the recycling stack
could get very large, resulting in a detriment to performance and (b)
iteration performance will suffer. In that scenario you may want to run a
benchmark limiting the minimum/maximum sizes of the groups, and tune it
until you find optimal usage.
Please note that the the fill, range and initializer-list constructors can also take group size parameters, making it possible to construct filled colonies using custom group sizes.
While I am happy to be proven wrong I suspect colony is it's own abstract data type. Some have suggested it is most similar to a multiset or bag, which is probably true, but from my perspective a bag or multiset utilizes key values. Colony does not utilize key values, is searchable and sortable, but does not give the sort of functionality one would find in a bag (for example counting the number of times a specific value occurs). Others have suggested a deque, but actually there are three necessary aspects to colony which make it function as it does, and which define any implementation:
In the case of my particular colony implementation I utilize a chained-group memory allocation pattern because it appears to have better performance than the alternatives, arguably though this could be any multiple-memory-block allocation strategy. For the skipfield I utilize the jump-counting skipfield but if a more efficient skipfield implementation were available for a given platform, that could be used. While it is preferable that any skipfield used not utilize branching code - as a common boolean skipfield does - because of the performance detriment caused by this, it is possible there could be a computer architecture where the cost of branching is much lower. The main necessity is that the skipfield enables O(1) ++ and -- operations (otherwise iterators cannot be valid within the standard). For the stack I am using plf::stack because according to benchmarks it is more efficient than the current standard library alternatives. But again, if a more efficient alternative were found it could be used instead.
If we look at a deque, the only aspect out of those three that it shares, is (1) - utilizing a multiple-memory-block allocation pattern. Which isn't a strong association. If we look at a multiset, an unordered one could be implemented on top of a colony utilizing an additional hash table, and it would be more efficient than the current multiset implementations as shown in the GCC and MSVC benchmarks. But the necessity to add something to make it a multiset, ie. to take and store key values, means colony is probably not a multiset, and probably it's own abstract data type.
For both colony and stack all operations which allocate memory have strong exception guarantees and will roll back if an exception occurs, except for operator = which has a basic exception guarantee (see below). For colony, iterators are bounded by asserts in debug mode, but unbounded in release mode, so it is possible for an incorrect use of an iterator (iterating past end(), for example) to trigger an out-of-bounds memory exception. These are the only two areas where exceptions can occur.
Similarly with plf::stack, top() and pop() operations are assert-checked against stack emptiness in debug mode, but performing these functions on an empty stack in release mode results in undefined behaviour.
The reason why operator = (for both colony and stack) only has a basic guarantee is they do not utilize the copy-swap idiom, as the copy-swap idiom significantly increases the chance of any exception occuring - this is because the most common way an exception tends to occur during a copy operation is due to a lack of memory space, and the copy-swap idiom doubles the memory requirement for a copy operation by constructing the copy before destructing the original data. This is doubly innappropriate in game development, which colony has been initially for, where memory constraints are often critical and the runtime lag from memory spikes can cause detrimental game performance. So in the case of colony and plf::stack, if a non-memory-allocation-based exception occurs during copy, the = operators will have already destructed their data, so the containers will be empty and cannot roll back - hence they have a basic guarantee, not a strong guarantee.
It was found under GCC that the high-modification scenario benchmarks suffered a 11% overall performance loss when iterator copy and move constructors/operators where not explicitly defined, possibly because GCC did not know to vectorise the operations without explicit code or because it implemented copy/swap idiom instead of copying directly. No performance gain was found under any tests when removing the copy and move constructors/operators.
Contact:
plf:: library and this page Copyright (c) 2016, Matthew Bentley


by Ivy Lu
Posted on July 20, 2017 at 1:00 PM
Hailee Steinfeld is so talented. I've watched this movie approximately 12398493 times because of her. I identify with this movie so strongly even though my best friend never slept with my brother.
In the grand scheme of things, losing a trusted friendship is a painful blow, but it usually isn’t the end of the world. If you’re an unpopular teenager, however, and the interloper is a sibling, it can feel like that. The personal nightmare of Nadine (Hailee Steinfeld), the embattled heroine of Kelly Fremon Craig’s smart, achingly bittersweet comedy, “The Edge of Seventeen” begins the night of a sleepover with her only close friend, Krista (Haley Lu Richardson), whom she catches in bed with her older brother, Darian (Blake Jenner, of “Everybody Wants Some!!”). Because Darian is the high school golden boy who could have anyone he wants, the discovery feels like a betrayal as sharp as a knife in the heart. After a cursory attempt at harmony in which the sweethearts invite Nadine to join them at a party, then abandon her to play beer pong, she insists Krista choose between them. When Krista refuses, Nadine banishes her from her life and spends much of the rest of the movie in a state of acute anguish and rage.


If you wish not to take the midterm exam, you may choose to answer the following writing prompt. You can turn the essay in at any time before the start of class on the day of exam. You must, however, notify me via the Moodle poll that you are choosing to write instead of sitting the exam.
At this point in the semester, we have read a half dozen articles, each of which has been an examination of a particular folk narrative form within a particular cultural context. Apply the analytical concerns of one article to the data of another. Be sure in your answer to make clear you understand the original context for both the analytical/theoretical approach upon which are drawing as well as the original context for the data upon which you are focused.
300-500 words (longer is better)


This Bible contradiction is from the Skeptic's Annotated Bible.
Verses
Yes. Solomon had a census (like his father David's) and God didn't mind at all.
2 Chronicles 2:17 View context
And Solomon numbered all the strangers that [were] in the land of Israel, after the numbering wherewith David his father had numbered them; and they were found an hundred and fifty thousand and three thousand and six hundred.
No. God killed 70,000 men because of David's census.
2 Samuel 24:1-17 View context
And again the anger of the LORD was kindled against Israel, and he moved David against them to say, Go, number Israel and Judah.
For the king said to Joab the captain of the host, which [was] with him, Go now through all the tribes of Israel, from Dan even to Beer-sheba, and number ye the people, that I may know the number of the people.
And Joab said unto the king, Now the LORD thy God add unto the people, how many soever they be, an hundredfold, and that the eyes of my lord the king may see [it]: but why doth my lord the king delight in this thing?
Notwithstanding the king's word prevailed against Joab, and against the captains of the host. And Joab and the captains of the host went out from the presence of the king, to number the people of Israel.
And they passed over Jordan, and pitched in Aroer, on the right side of the city that [lieth] in the midst of the river of Gad, and toward Jazer:
Then they came to Gilead, and to the land of Tahtim-hodshi; and they came to Dan-jaan, and about to Zidon,
And came to the strong hold of Tyre, and to all the cities of the Hivites, and of the Canaanites: and they went out to the south of Judah, [even] to Beer-sheba.
So when they had gone through all the land, they came to Jerusalem at the end of nine months and twenty days.
And Joab gave up the sum of the number of the people unto the king: and there were in Israel eight hundred thousand valiant men that drew the sword; and the men of Judah [were] five hundred thousand men.
And David's heart smote him after that he had numbered the people. And David said unto the LORD, I have sinned greatly in that I have done: and now, I beseech thee, O LORD, take away the iniquity of thy servant; for I have done very foolishly.
For when David was up in the morning, the word of the LORD came unto the prophet Gad, David's seer, saying,
Go and say unto David, Thus saith the LORD, I offer thee three [things]; choose thee one of them, that I may [do it] unto thee.
So Gad came to David, and told him, and said unto him, Shall seven years of famine come unto thee in thy land? or wilt thou flee three months before thine enemies, while they pursue thee? or that there be three days' pestilence in thy land? now advise, and see what answer I shall return to him that sent me.
And David said unto Gad, I am in a great strait: let us fall now into the hand of the LORD; for his mercies [are] great: and let me not fall into the hand of man.
So the LORD sent a pestilence upon Israel from the morning even to the time appointed: and there died of the people from Dan even to Beer-sheba seventy thousand men.
And when the angel stretched out his hand upon Jerusalem to destroy it, the LORD repented him of the evil, and said to the angel that destroyed the people, It is enough: stay now thine hand. And the angel of the LORD was by the threshingplace of Araunah the Jebusite.
And David spake unto the LORD when he saw the angel that smote the people, and said, Lo, I have sinned, and I have done wickedly: but these sheep, what have they done? let thine hand, I pray thee, be against me, and against my father's house.
1 Chronicles 21:2 View context
And David said to Joab and to the rulers of the people, Go, number Israel from Beer-sheba even to Dan; and bring the number of them to me, that I may know [it].


Figure 1: Transform parameters
These parameters are used in the Transform process, in which the 1-D correlation function and second moment of the data are calculated from the extrapolated data. The user must specify the maximum d-spacing and step in d for the correlation function, and can also choose whether to re-transform the 1-D correlation function to obtain a simulated scattering curve which can be compared to the original data.
To generate a volume fraction profile, select Transform
type: Hilbert (see Hilbert).


Planets form and obtain their compositions in disks around young stars, and the outcome of this process is intimately linked to the disk chemistry and structure.
'Molecules with ALMA at Planet-forming Scales' (MAPS) is an ALMA Large Program (2018.1.01055.L) designed to expand our understanding of the chemistry of planet formation by exploring protoplanetary disk chemical structures down to 10 au scales.
Together these observations provide constraints on a range of disk properties related to planet formation. In particular, MAPS was designed to answer the following key questions...
The MAPS program focuses on five disks — around IM Lup, GM Aur, AS 209, HD 163296, and MWC 480 — in which dust substructures are detected and planet formation appears to be ongoing. We observed these disks in 4 spectral setups in ALMA Band 3 and Band 6, which together cover approximately 50 molecular lines from 20 different chemical species.
The program overview and data calibration is presented in MAPS I, and the imaging strategy in MAPS II. Some key science results are:
Together, our results demonstrate the utility of deep, high-resolution ALMA observations of molecular lines to explore the chemistry that affects and probes planet formation in disks.
In addition to the MAPS publications, all data products and analysis scripts are publicly available from our dedicated download page.


Building a personalized list of best practices will help you:
Enhancing your negotiation skills depends, though, on the effort you invest in discovering and applying the right lessons from your negotiation experience. The good news is that thoughtful preparation does not take much time. Remember: you never want to be less prepared than the other negotiator at the table.
This feature on the app allows review what has worked well for you in the past (and what you would do differently in the future). The Negotiation Performance section allows you to track your performance over time. You can also navigate back to the Tips offered in the self-assessment feature.
To take full advantage of the app, periodically review the kinds of negotiations you engage in, how important they are, and how you feel you've performed. Look for general patterns and trends.


---
layout: post
esbma_id: 1767
title: Ten Quirky Issues with Cross-Platform Version Control
date: 2009-04-13 18:16:27
---
A big chunk of the software industry today can mostly ignore
the issues of multiple platforms, for one of the following reasons:
- They only support Windows. It's got like 90% market
share, so why not?
- They serve a web application and don't care what the end
user is actually using as long as their browser works.
But version control tools involve more cross-platform
concerns than most other kinds of software. Neither of the reasons above tends
to work very well.
- If a software team has 450 Windows users and 50 people on
Mac or Unix, then a Windows-only solution just won't do.
- Since a primary task of a version control tool is to
manage source code trees on the user's hard disk, a web application just
won't do.
So, even as most coders have moved on to a world where they
can remain blissfully ignorant of the problems of writing software for multiple
operating systems, those of us who create version control tools are still wrestling
with those problems.
And in fact, I claim that our challenges are tougher than most.
Version control users ask for the darndest things, especially in the big
enterprise companies. It's easy to believe that all you need is Windows, Mac,
Linux and maybe Solaris. Then you find out just how prevalent things like AIX
and HPUX are. Terms like "Irix" and "Win95" and "mainframe" get tossed around
until you're numb and nothing surprises you anymore. When somebody asks for a
port to an arcane platform, you roll your eyes and wonder is if it uses 8-bit
bytes or not.
Worse than that, version control vendors aren't just porting
to oddball operating systems. We actually have to make our software interoperate
across all those environments.
And that's where things start to get quirky.
- On a Linux system, create a file called "README". In the
same directory, create a file called "readme". Check them both in. Now
go to a Mac and check them both out. Since the Mac file system is
[usually] case insensitive, something bad is going to happen. Same goes
for Windows/NTFS.
- On a Mac, checkin a file called "PRN". Check it out on a
Windows system. That file name is not
allowed under Windows, for backward compatibility with MS-DOS..
- Under Linux, checkin a file with a name that ends in a
dot. Check it out under Windows. The trailing dot is probably gone. Now
check the file back in and go back to your Unix system. If your version
control system handled this badly, you've probably got two copies of the
file, one with the trailing dot, and one without. Same goes for a
trailing space.
- On a Linux system, checkin a file with a path that is 261
characters long. Check it out under Windows. This might work. It
probably won't. It kind of depends on whether .NET is involved or not.
There's a \\?\ trick to get around the limitations of the Win32 layer, but
the .NET libraries don't use it.
- On a Mac, checkin a file that has a resource fork and
some Finder info. Check it out on a Linux machine. What happens? Did
stuff show up as xattrs?
Should it have? On that same Linux machine, make a change and check it
back in. Then check it out on the Mac again. Is the Finder info still
there?
- On a Linux machine, checkin a file with a colon in the
name. Check it out on a Mac. Not sure what'll happen, but it probably
won't be what you want.
- On a Windows machine, checkin a file with a name that
begins with a dash. Now check it out under Linux and try
manipulating it with command-line utilities. Apps will think the
filename is a command-line option.
What if somebody creates a file
named "-rf *" and a Linux user tries to rm it? OK, bad example.
The point remains: Filenames which begin with a dash may cause more
problems on some platforms than on others.
- On a Linux machine, create a source code file and check it
in. Check it out on Windows and open it with notepad. The line-endings are Unix-standard
LF, but Windows apps expect CRLF, so notepad shows the entire file as one
line. Now open the same file under Visual Studio. The file looks fine
now. Now edit a few lines in the middle of the file, check it back in,
and check it out on Linux again. The lines you edited are messed up.
- On a recent Ubuntu Linux system, create a file called "Español".
Do the same thing on Mac OS X 10.5. These two files have the same name,
but even though you are [probably] using the utf-8 encoding of Unicode on
both systems, the bytes which represent that name do not match. On the
Linux machine, the file name [probably] will be in NFC normalized form (Espa\u00f1ol).
On the Mac, everything gets normalized to NFD (Espan\u0303ol). When you
check these files in and start working with them, bad things will happen
unless your version control tool understands
what's going on and deals with it appropriately.
- On a Unix machine, checkin a symbolic link. Check it out
on Windows. What happens?
Like I said, things get quirky.


The webmail client feature will be free forever, and we see it as a promotion feature.
In the future we will roll out more useful functionality and we may charge those services to recoup our cost


The unlabled bars now have values! I haven't figured out how to put in units or labels yet, but I'm working on it.


Prior to jQuery Mobile 1.1, true fixed toolbar support was contingent on native browser support for the CSS property overflow-scrolling: touch
, which is currently only supported in iOS5. As of version 1.1, jQuery Mobile no longer uses this CSS property at all. We've removed all internal usage of this property in the framework, but we've left it defined globally on the $.mobile object to reduce the risk that its removal will cause trouble with existing applications. This property is flagged for removal, so please update your code to no longer use it. The support test for this property, however, remains defined under $.support
and we have no plans to remove that test at this time.
Currently, both the page you're viewing and the one you're navigating to are sitting next to each other in the viewport, which lets us leverage native scrolling for the broadest possible device support. The downside to this approach is that since both pages share the same viewport, when a page transition starts, we must first scroll to the top of the document, then start animating to ensure that the pages are lined up vertically. If you hit the Back button, we need to scroll up, transition, then restore the previous scroll position. Since mobile browsers are pretty slow, these scroll movements can detract from the flow of the experience.
The way to really improve this situation is to have both pages in separate containers, each with its own internal scroll bar. The means no more scrolling the document or needing to restore scroll positions for a smoother experience. It also has the benefit of making fixed toolbars very easy to implement by simply placing them outside the containers with internal scrolling.
To leverage iOS5′s support for a touch-targeted version of overflow:auto
which allows for internal scrolling regions with the native momentum scrolling, we've added a feature called touchOverflow that leverages these new CSS capabilities to enable us to bring both true “fixed” toolbars and super smooth transitions to iOS5, all by using web standards and very little additional code.
A feature called touchOverflowEnabled
is designed to leverage the upcoming wave of browsers that support overflow scrolling in CSS. Note that this feature is off by default to give us more time to test and debug this for best performance but we hope to turn it on by default at a later point. Here's how to enable this global option:
<script>
$(document).bind("mobileinit", function(){
$.mobile.touchOverflowEnabled = true;
});
</script>
When this feature is activated, the framework looks for browser support for both the overflow:
and -webkit-overflow-scrolling:touch
CSS properties. In browsers that support both, it switches to a dual page container model with native overflow: scrolling within each, which brings true fixed toolbars smooth transitions. Coupled with iOS’s already excellent hardware-accelerated transitions, it's now possible to build interfaces that are very close to native performance.
To demo this feature, check out this page in iOS5.
Nothing is perfect, especially a new feature, so there are a few downsides to keep in mind. When activating this feature:
overflow:
in iOS5. This was a pretty random phenomenon but is not acceptable so we've added a translate-z
CSS property which forces iOS to render the contents. The downside with this fix is that when a transform is applied, all elements are set to position:relative which can cause issues in your layout.-webkit-overflow-scrolling:touch
property seems to disable the events to scroll you to the top of the page when the time is tapped in the status bar. We hope Apple fixes this because it's a very useful feature.overflow:
and -webkit-overflow-scrolling:touch
properties are set, iOS appears to ignore any overflow:hidden properties on the parent, which is the page in our case. So if you have an image or code block that is wider than the viewport, horizontal scrolling will be seen. Yes, but there’s a catch. Both Android Honeycomb and the Blackberry PlayBook support overflow:
properties, but we found in testing that their implementation of overflow wasn't smooth enough, so pages would stutter and hang during scrolling, leading to an unusable experience. We're working with device makers to ensure that they are included when performance improves.
More importantly, targeting overflow correctly is a major issue. If we simply placed an overflow: auto
CSS rule on the pages, other popular mobile platforms like older versions of Android and iOS would essentially just clip off the content and make it effectively inaccessible (yes, you can do a two-finger scroll gesture in iOS but nobody knows that). The smart thing about Apple’s implementation for iOS5 is that they added an additional CSS property -webkit-overflow-scrolling:touch
that allows us to test for this touch scrolling property and, if supported, add in the overflow
rules for just those browsers. This is the only safe way to target overflow without resorting to complex and unmaintainable user agent detection.
We will be working with device and browser makers to encourage support for both these CSS-based properties because we strongly believe that this a critical piece needed to build rich mobile web apps. The project will add any vendor-prefixed additions to touch scrolling property if, for example, Opera, Firefox or Microsoft added this support. Once people see how much better page transitions and fixed toolbars are on iOS5, we’re hoping this will be supported quickly by other browsers. JS-based scroller scripts may still have a place in this new world as a polyfill for browsers that don’t yet support these new CSS capabilities but we see this as a brief, interim tool in the evolution of the mobile web.
Generally touchOverflow is only enabled on devices that support touch-scrolling of overflow areas, not desktop browsers. This can make it difficult to debug problems with the touchOverflow feature. To enable touchOverflow on all browsers, use the following code:
<script>
$(document).bind("mobileinit", function() {
$.support.touchOverflow = true;
$.mobile.touchOverflowEnabled = true;
});
</script>


Nickname: {{ nickname }}
Email: {{ email }}
Email Verified: {{ email_verified }}
Last Updated: {{ updated_at }}


Noaax v0.1.0 Noaax.NoaaService View Source
Fetch NOAA web page
Link to this section Summary
Functions
http://w1.weather.gov/xml/current_obs/STATION.xml
Fetch station data, will return a tuple in the form of
{ :ok, body }
or { :error, body }
Fetch station data, will return a tuple in the form of
{ :ok, body }
or { :error, body }
Return {:ok, body} if http status code is 200
http://w2.weather.gov/xml/current_obs/seek.php?state=STATENAME&Find=Find
Link to this section Functions
http://w1.weather.gov/xml/current_obs/STATION.xml
Fetch station data, will return a tuple in the form of
{ :ok, body }
or { :error, body }
Fetch station data, will return a tuple in the form of
{ :ok, body }
or { :error, body }
Return {:ok, body} if http status code is 200
Return {:error, body} if http status code is not 200
http://w2.weather.gov/xml/current_obs/seek.php?state=STATENAME&Find=Find


I finished exams on the 14th of April, two weeks earlier than normal because the business school works on their own schedule. On the 18th, I flew to Paris to meet up with my parents and my Uncle Brad. I cut it extremely close with my Chinese visa, picking it up around 1pm on the day when my flight was at leaving at 7, but it luckily worked out.
We arrived in Paris early morning after taking a red eye flight, so we started the trip with a quick bite to eat followed by a nap. In the afternoon we walked around close to our hotel, passing by the Louvre, wandering over to the island, and the Notre Dame Cathedral. At dinner I ordered a shrimp and avocado salad to start, and couldn’t believe it came with a full avocado. I don’t think I’ve ever gotten that back in Canada. This still blows my mind.
The second day we visited the Musée D’Orsay and saw lots of beautiful art. The day after we went out to Versailles and it was pretty hard to believe it was actually someone’s place of residence. From the hall of mirrors to the courtyard, it seemed never ending.
That night, we went to see the Eiffel Tower just as the sun was setting.
One of my favourites parts of the trip was the Catacombes. Thousands of people were buried underground Paris, and you can walk through a 2km stretch of it. Really, really cool.
To finish off our stint in Paris before heading up North, we checked out the Arc de Triomphe and the Avenue des Champs-Élysées.
On the same day we arrived in Arras we went to the Canadian memorial at Vimy ridge. The shame with pictures is that they really do not show the scale of these things. Only having seen pictures of the monument (mainly on the $10 bill), actually arriving there was something else. It baffled me how there were cemeteries everywhere you would look.
This next picture was probably the most interesting thing of the trip. Every single site we visited had other people at it, even if it was a cemetery of only a couple hundred. Pictured below is the German cemetery, with 4 people buried for each cross. 44,000 people were buried in this one site, and no one visited. The parking lot was so small we actually drove past it the first time.
A French cemetery is below, along with a great memorial. It has the list of every single person that fought in the war listed without country, race, religion, anything. Just all of the names blended together.
The next day we took a trip to Beaumont Hamel, the Newfoundland memorial site. On July 1st 1916, 780 Newfoundlanders “attacked” the Germans at this site. 68 answered role call the next day.  They never stood a chance. The danger tree (pictured left) was a landmark where just about no one got past. It really was not far from where they came out of the trenches. The winding trenches were all around, and the Newfoundlanders would have had to jump out of the only to know to be shot down almost immediately.
This Caribou memorial is one of five in France & Belgium all commemorating the casualties faced by the Newfoundland Regiment in the first World War, with another back in St. John’s.
Even the tiny town of Arras had lots of history, and served as a home base for people to go to in order to visit all the memorial sites nearby. This below is a picture of the city center at night.
The next day we took a quick road trip to Belgium to go to the Flanders field museum where there was actually an exhibition on Canada on display, so that was great. You could get to the roof of the church for a great view, but inside there was so much. It was really well done. Below is a hat from the Newfoundland Regiment, and a picture of the church right behind the museum.
After having a bit of a scare about not being able to get back to Paris (there was a train strike and our initial train got cancelled), we eventually got back without too much of a delay and went on a gorgeous dinner boat tour.
That was it for France! The next morning (April 27th) I got up early for a flight to San Francisco via Toronto for my first Cansbridge Fellowship Conference. With one day of free time, we rented bikes and went all around, eventually over the bridge to Sausalito. A bit jet lagged, we had an early night to get ready for the start of the weekend. I didn’t take many pictures, but the second picture of the bridge was taken from Google’s office! Definitely loved SF, and can’t wait to get back.


The first time I tried the sage, I had no idea what was coming.
I read as many reports as possible, and did the necessary research before my venture. I chose my whole house to be the setting, but locked all doors so that to get outside would take a bit of effort (and conscious thought, most importantly). I chose not to have a sitter, simply because I knew no one who had tried it before, and that can often cause even more confusion and possibly even fear in the first-timer. I had read the reports of little success, so I knew there was a chance that I would be disappointed. The leaves were dry and brittle, so I crumbled one leaf into the bowl of my bong (water cooled is a must).
1st hit (all held for 25 seconds): disorientation immediately following exhale
2nd hit: I knew I'd be going somewhere, but everyone else claimed 4 hits, so I proceeded.
3rd hit: 'here we go' I said to myself.
I sat back a minute and knew that I was about to embark on something new and bizarre. After about 30-40 seconds of sitting in bewilderment, I opted for a 4th hit:
KABLAM!!
It was as though I was in one of those dreams where you feel you've already awakened, and begin to act and move around. When I walked, I felt as though my legs stopped moving, and I was on a moving walk-way. My only mission was to get into the next room to grab a blanket. I returned successfully (but don't remember exactly). Next thing I new I was 'waking up' on the couch thinking 'it was a delusion, and I'm still here on the couch' except I had the blanket in hand. At that point my mind started going into loops not unlike those of a high-level acid trip. [Note: nothing else that occurred can be equated to acid or any other substance.]
I had a strong feeling that 'they' were observing me. Who's they? I have no idea, but they weren't belligerent; merely observant. I also realized that my vision, though not impaired, was irrelevant to the state I was in. It was simply not needed. I couldn't tell if my eyes were open or closed, anyway, so I'm not sure if the things around me really existed or not. [Note: I stand corrected, the only other 'acidic' thing that occurred was the visuals that are subtle like the tiny, tiny bubbles that form at the edge of all objects; usually with a reddish hue.]
I wasn't ready for all this. I didn't expect to be here, so I began to panic a little. I started talking out loud, and identifying myself. It's a little trick I learned after a very,
very bad acid trip I had once. I had my 'reality item' before I entered this world. I grabbed it and said 'you are in your house alone, you smoked a very powerful substance, and the experience will soon be within your grasp.'
I also made sure to check the time before hand so that I could always check it in case things started to go awry. I checked the time, and 20 minutes had passed. I could feel things start to die down. The come-down lasted approx. 1 hour 30 min. It was gradual and a bit uncomfortable.
Lesson? Now I know where it takes me, and I know who's there to greet me, so I won't be uncomfortable next time. The locked doors was a good idea. The Boards of Canada on the stereo was not. If you plan on listening to music, make sure it's the kind that isn't a head trip even when you are sober.
Today I tried one hit just out of curiosity:
Again I was amazed and somewhat unprepared, but you only lose touch briefly for about 1 minute. They were there again, but they weren't too clear, and left shortly thereafter.
1 hit: tells them I'm coming but can't stay long
2-3 hits: I can stay for a bit, but don't want to leave the ground
4 hits: as I said: 'KABLAM'
5-more: I don't think I'm interested in going there just because.
Since Salvia divinorum has a small duration, one can't really plan a long trip (but the distance travelled is truly immeasurable). Now that experimentation is over, from this point onward I plan only to use it as more of a shamanistic tool.
Experience Reports are the writings and opinions of the individual authors who submit them.
Some of the activities described are dangerous and/or illegal and none are recommended by Erowid.


StarPhone
Add Schedule For StarPhone
Schedule Definition
Schedule name
Active
Non-configured group
Assigned group
>>
<<
Statistics options
Statistics:
Stats related to:

Group
Agent
Avg Handling Time
Net Conv. Rate
Sales/Hour
Productivity Ratio
Busy Factor
Total Login Time
Total Calls
Waiting Calls
Abandoned Calls
Message color:
Pick
Broadcast Period (min)
1
5
15
30
1h
2h
Alerts options
Broadcast Period (min)
1
5
15
30
1h
2h
Save
Manager
Add Schedule For Manager
Schedule Definition
Schedule name
Active
Alerts options
Broadcast Period (min)
1
5
15
30
1h
2h
Save


Toggle navigation
SG
About
My Projects
Education
Achievements
SKILLS
INTERESTS
C
C++
Java
Python
Octave
App Development
Web Development
Machine Learning
Competitive Programming


metric_space
2019年12月7日
metric space
度量空间metric space是一个度量metric的集合，通常度量metric是定义在某点集上的函数，
metric
度量必须满足下面四个条件
the distance from a point to itself is zero
the distance between two distict point is positive
the distance from A to B is the same as the distance from B to A
the distance from A to B(directly) is less than or equal to the distance from A to B via any third point C
表达
通常我们使用有序对(M,d)来表示度量空间，M是集合，d是集合M上的度量


# Copyright 2021 JD.com, Inc., JD AI
"""
@author: Yehao Li
@contact<EMAIL_ADDRESS>"""
import numpy as np
import torch
import torch.nn as nn
from torch.autograd import Variable
from xmodaler.config import configurable
from xmodaler.config import kfg
from .build import LOSSES_REGISTRY
[docs]@LOSSES_REGISTRY.register()
class BatchTriplet(nn.Module):
[docs] @configurable
def __init__(self, margin, max_violation):
super(BatchTriplet, self).__init__()
self.margin = margin
self.max_violation = max_violation
[docs] @classmethod
def from_config(cls, cfg):
return {
"margin": cfg.LOSSES.MARGIN,
"max_violation": cfg.LOSSES.MAX_VIOLATION
}
[docs] def forward(self, outputs_dict):
scores = outputs_dict[kfg.OUTPUT]
ids = np.array(outputs_dict[kfg.IDS])
labels = np.expand_dims(ids, axis=1) == np.expand_dims(ids, axis=0)
labels = torch.from_numpy(labels).cuda()
# compute image-sentence score matrix
batch_size = scores.size(0)
diagonal = scores.diag().view(batch_size, 1)
d1 = diagonal.expand_as(scores)
d2 = diagonal.t().expand_as(scores)
# compare every diagonal score to scores in its column
# caption retrieval
cost_s = (self.margin + scores - d1).clamp(min=0)
# compare every diagonal score to scores in its row
# image retrieval
cost_im = (self.margin + scores - d2).clamp(min=0)
# clear diagonals
mask = torch.eye(scores.size(0)) > .5
I = Variable(mask)
if torch.cuda.is_available():
I = I.cuda()
cost_s = cost_s.masked_fill_(I, 0)
cost_im = cost_im.masked_fill_(I, 0)
cost_s = cost_s.masked_fill_(labels, 0)
cost_im = cost_im.masked_fill_(labels, 0)
# keep the maximum violating negative for each query
if self.max_violation:
cost_s = cost_s.max(1)[0]
cost_im = cost_im.max(0)[0]
triplet_num = len(cost_s) + len(cost_im)
loss = cost_s.sum() + cost_im.sum()
else:
cost_s = torch.masked_select(cost_s, cost_s > 0)
cost_im = torch.masked_select(cost_im, cost_im > 0)
triplet_num = len(cost_s) + len(cost_im)
loss = cost_s.mean() + cost_im.mean()
return { 'BatchTriplet Loss': loss, "Triplet_num": triplet_num }


Please feel free to contact me at<EMAIL_ADDRESS>at any time or through my phone number at<PHONE_NUMBER>.
If you are contacting me for tutoring inquiries, please include answers to the following questions, if possible and applicable, alongside any other information that you think I should know.