Skip to content

Commit

Permalink
Add basic LRU eviction for the plasma store. (ray-project#26)
Browse files Browse the repository at this point in the history
* Basic functionality for LRU eviction.

* Test eviction.

* Factor out eviction policy.

* Move delete_object into eviction policy.

* Replace array of released objects with an LRU cache (hash table + doubly linked list).

* Finish rebase on master.

* Move actual object deletion away from eviction policy and into plasma store.

* Small fixes.

* Fixes.

* Make remove_object_from_lru_cache always remove the object.

* Minor formatting and comments.

* Pass in allowed memory as argument to Plasma store.

* Small fix.
  • Loading branch information
robertnishihara authored and pcmoritz committed Nov 6, 2016
1 parent 90a2aa4 commit efe8a29
Show file tree
Hide file tree
Showing 13 changed files with 614 additions and 78 deletions.
4 changes: 2 additions & 2 deletions install-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ if [[ $platform == "linux" ]]; then
# These commands must be kept in sync with the installation instructions.
sudo apt-get update
sudo apt-get install -y git cmake build-essential autoconf curl libtool python-dev python-numpy python-pip libboost-all-dev unzip
sudo pip install funcsigs colorama redis
sudo pip install funcsigs colorama psutil redis
sudo pip install --upgrade git+git://github.com/cloudpipe/cloudpickle.git@0d225a4695f1f65ae1cbb2e0bbc145e10167cce4 # We use the latest version of cloudpickle because it can serialize named tuples.
elif [[ $platform == "macosx" ]]; then
# These commands must be kept in sync with the installation instructions.
brew install git cmake automake autoconf libtool boost
sudo easy_install pip
sudo pip install numpy funcsigs colorama redis --ignore-installed six
sudo pip install numpy funcsigs colorama psutil redis --ignore-installed six
sudo pip install --upgrade git+git://github.com/cloudpipe/cloudpickle.git@0d225a4695f1f65ae1cbb2e0bbc145e10167cce4 # We use the latest version of cloudpickle because it can serialize named tuples.
fi

Expand Down
12 changes: 8 additions & 4 deletions lib/python/ray/services.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from __future__ import print_function

import psutil
import os
import sys
import time
import random
import signal
import sys
import subprocess
import string
import random
import time

# Ray modules
import config
Expand Down Expand Up @@ -90,13 +91,16 @@ def start_objstore(node_ip_address, redis_address, cleanup=True):
this process will be killed by serices.cleanup() when the Python process
that imported services exits.
"""
# Let the object store use a fraction of the system memory.
system_memory = psutil.virtual_memory().total
plasma_store_memory = int(system_memory * 0.75)
plasma_store_filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../plasma/build/plasma_store")
if RUN_PLASMA_STORE_PROFILER:
plasma_store_prefix = ["valgrind", "--tool=callgrind", plasma_store_filepath]
else:
plasma_store_prefix = [plasma_store_filepath]
store_name = "/tmp/ray_plasma_store{}".format(random_name())
p1 = subprocess.Popen(plasma_store_prefix + ["-s", store_name])
p1 = subprocess.Popen(plasma_store_prefix + ["-s", store_name, "-m", str(plasma_store_memory)])

manager_name = "/tmp/ray_plasma_manager{}".format(random_name())
p2, manager_port = plasma.start_plasma_manager(store_name, manager_name, redis_address, run_profiler=RUN_PLASMA_MANAGER_PROFILER)
Expand Down
3 changes: 2 additions & 1 deletion src/photon/test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import plasma

USE_VALGRIND = False
PLASMA_STORE_MEMORY = 1000000000

class TestPhotonClient(unittest.TestCase):

Expand All @@ -23,7 +24,7 @@ def setUp(self):
# Start Plasma.
plasma_executable = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../../plasma/build/plasma_store")
plasma_socket = "/tmp/plasma_store{}".format(random.randint(0, 10000))
self.p2 = subprocess.Popen([plasma_executable, "-s", plasma_socket])
self.p2 = subprocess.Popen([plasma_executable, "-s", plasma_socket, "-m", str(PLASMA_STORE_MEMORY)])
time.sleep(0.1)
self.plasma_client = plasma.PlasmaClient(plasma_socket)
scheduler_executable = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../build/photon_scheduler")
Expand Down
4 changes: 2 additions & 2 deletions src/plasma/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ clean:
$(BUILD)/manager_tests: test/manager_tests.c plasma.h plasma_client.h plasma_client.c plasma_manager.h plasma_manager.c fling.h fling.c common
$(CC) $(CFLAGS) $(TEST_CFLAGS) -o $@ test/manager_tests.c plasma_manager.c plasma_client.c fling.c ../common/build/libcommon.a ../common/thirdparty/hiredis/libhiredis.a

$(BUILD)/plasma_store: plasma_store.c plasma.h fling.h fling.c malloc.c malloc.h thirdparty/dlmalloc.c common
$(CC) $(CFLAGS) plasma_store.c fling.c malloc.c ../common/build/libcommon.a -o $(BUILD)/plasma_store
$(BUILD)/plasma_store: plasma_store.c plasma.h eviction_policy.c fling.h fling.c malloc.c malloc.h thirdparty/dlmalloc.c common
$(CC) $(CFLAGS) plasma_store.c eviction_policy.c fling.c malloc.c ../common/build/libcommon.a -o $(BUILD)/plasma_store

$(BUILD)/plasma_manager: plasma_manager.c plasma.h plasma_client.c fling.h fling.c common
$(CC) $(CFLAGS) plasma_manager.c plasma_client.c fling.c ../common/build/libcommon.a ../common/thirdparty/hiredis/libhiredis.a -o $(BUILD)/plasma_manager
Expand Down
210 changes: 210 additions & 0 deletions src/plasma/eviction_policy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#include "eviction_policy.h"

#include "utlist.h"

/** An element representing a released object in a doubly-linked list. This is
* used to implement an LRU cache. */
typedef struct released_object {
/** The object_id of the released object. */
object_id object_id;
/** Needed for the doubly-linked list macros. */
struct released_object *prev;
/** Needed for the doubly-linked list macros. */
struct released_object *next;
} released_object;

/** This type is used to define a hash table mapping the object ID of a released
* object to its location in the doubly-linked list of released objects. */
typedef struct {
/** Object ID of this object. */
object_id object_id;
/** A pointer to the corresponding entry for this object in the doubly-linked
* list of released objects. */
released_object *released_object;
/** Handle for the uthash table. */
UT_hash_handle handle;
} released_object_entry;

/** The part of the Plasma state that is maintained by the eviction policy. */
struct eviction_state {
/** The amount of memory (in bytes) that we allow to be allocated in the
* store. */
int64_t memory_capacity;
/** The amount of memory (in bytes) currently being used. */
int64_t memory_used;
/** A doubly-linked list of the released objects in order from least recently
* released to most recently released. */
released_object *released_objects;
/** A hash table mapping the object ID of a released object to its location in
* the doubly linked list of released objects. */
released_object_entry *released_object_table;
};

/* This is used to define the array of object IDs used to define the
* released_objects type. */
UT_icd released_objects_entry_icd = {sizeof(object_id), NULL, NULL, NULL};

eviction_state *make_eviction_state(int64_t system_memory) {
eviction_state *state = malloc(sizeof(eviction_state));
/* Find the amount of available memory on the machine. */
state->memory_capacity = system_memory;
state->memory_used = 0;
state->released_objects = NULL;
state->released_object_table = NULL;
return state;
}

void free_eviction_state(eviction_state *s) {
/* Delete each element in the doubly-linked list. */
released_object *element, *temp;
DL_FOREACH_SAFE(s->released_objects, element, temp) {
DL_DELETE(s->released_objects, element);
free(element);
}
/* Delete each element in the hash table. */
released_object_entry *current_entry, *temp_entry;
HASH_ITER(handle, s->released_object_table, current_entry, temp_entry) {
HASH_DELETE(handle, s->released_object_table, current_entry);
free(current_entry);
}
/* Free the eviction state. */
free(s);
}

void add_object_to_lru_cache(eviction_state *eviction_state,
object_id object_id) {
/* Add the object ID to the doubly-linked list. */
released_object *linked_list_entry = malloc(sizeof(released_object));
linked_list_entry->object_id = object_id;
DL_APPEND(eviction_state->released_objects, linked_list_entry);
/* Check that the object ID is not already in the hash table. */
released_object_entry *hash_table_entry;
HASH_FIND(handle, eviction_state->released_object_table, &object_id,
sizeof(object_id), hash_table_entry);
CHECK(hash_table_entry == NULL);
/* Add the object ID to the hash table. */
hash_table_entry = malloc(sizeof(released_object_entry));
hash_table_entry->object_id = object_id;
hash_table_entry->released_object = linked_list_entry;
HASH_ADD(handle, eviction_state->released_object_table, object_id,
sizeof(object_id), hash_table_entry);
}

void remove_object_from_lru_cache(eviction_state *eviction_state,
object_id object_id) {
/* Check that the object ID is in the hash table. */
released_object_entry *hash_table_entry;
HASH_FIND(handle, eviction_state->released_object_table, &object_id,
sizeof(object_id), hash_table_entry);
/* Only remove the object ID if it is in the LRU cache. */
CHECK(hash_table_entry != NULL);
/* Remove the object ID from the doubly-linked list. */
DL_DELETE(eviction_state->released_objects,
hash_table_entry->released_object);
/* Free the entry from the doubly-linked list. */
free(hash_table_entry->released_object);
/* Remove the object ID from the hash table. */
HASH_DELETE(handle, eviction_state->released_object_table, hash_table_entry);
/* Free the entry from the hash table. */
free(hash_table_entry);
}

int64_t choose_objects_to_evict(eviction_state *eviction_state,
plasma_store_info *plasma_store_info,
int64_t num_bytes_required,
int64_t *num_objects_to_evict,
object_id **objects_to_evict) {
int64_t num_objects = 0;
int64_t num_bytes = 0;
/* Figure out how many objects need to be evicted in order to recover a
* sufficient number of bytes. */
released_object *element, *temp;
DL_FOREACH_SAFE(eviction_state->released_objects, element, temp) {
if (num_bytes >= num_bytes_required) {
break;
}
/* Find the object table entry for this object. */
object_table_entry *entry;
HASH_FIND(handle, plasma_store_info->objects, &element->object_id,
sizeof(object_id), entry);
/* Update the cumulative bytes and the number of objects so far. */
num_bytes += (entry->info.data_size + entry->info.metadata_size);
num_objects += 1;
}
/* Construct the return values. */
*num_objects_to_evict = num_objects;
if (num_objects == 0) {
*objects_to_evict = NULL;
} else {
*objects_to_evict = (object_id *) malloc(num_objects * sizeof(object_id));
int counter = 0;
DL_FOREACH_SAFE(eviction_state->released_objects, element, temp) {
if (counter == num_objects) {
break;
}
(*objects_to_evict)[counter] = element->object_id;
/* Update the LRU cache. */
remove_object_from_lru_cache(eviction_state, element->object_id);
counter += 1;
}
}
/* Update the number used. */
eviction_state->memory_used -= num_bytes;
return num_bytes;
}

void object_created(eviction_state *eviction_state,
plasma_store_info *plasma_store_info,
object_id obj_id) {
add_object_to_lru_cache(eviction_state, obj_id);
}

void require_space(eviction_state *eviction_state,
plasma_store_info *plasma_store_info,
int64_t size,
int64_t *num_objects_to_evict,
object_id **objects_to_evict) {
/* Check if there is enough space to create the object. */
int64_t required_space =
eviction_state->memory_used + size - eviction_state->memory_capacity;
if (required_space > 0) {
/* Try to free up as much free space as we need right now. */
LOG_DEBUG("not enough space to create this object, so evicting objects");
/* Choose some objects to evict, and update the return pointers. */
int64_t num_bytes_evicted = choose_objects_to_evict(
eviction_state, plasma_store_info, required_space, num_objects_to_evict,
objects_to_evict);
printf("Evicted %" PRId64 " bytes.\n", num_bytes_evicted);
LOG_INFO(
"There is not enough space to create this object, so evicting "
"%" PRId64 " objects to free up %" PRId64 " bytes.\n",
*num_objects_to_evict, num_bytes_evicted);
CHECK(num_bytes_evicted >= required_space);
} else {
*num_objects_to_evict = 0;
*objects_to_evict = NULL;
}
eviction_state->memory_used += size;
}

void begin_object_access(eviction_state *eviction_state,
plasma_store_info *plasma_store_info,
object_id obj_id,
int64_t *num_objects_to_evict,
object_id **objects_to_evict) {
/* If the object is in the LRU cache, remove it. */
remove_object_from_lru_cache(eviction_state, obj_id);
*num_objects_to_evict = 0;
*objects_to_evict = NULL;
}

void end_object_access(eviction_state *eviction_state,
plasma_store_info *plasma_store_info,
object_id obj_id,
int64_t *num_objects_to_evict,
object_id **objects_to_evict) {
/* Add the object to the LRU cache.*/
add_object_to_lru_cache(eviction_state, obj_id);
*num_objects_to_evict = 0;
*objects_to_evict = NULL;
}
Loading

0 comments on commit efe8a29

Please sign in to comment.