diff --git a/benchmarks/serialization.py b/benchmarks/serialization.py
index d2baa87..453fe24 100644
--- a/benchmarks/serialization.py
+++ b/benchmarks/serialization.py
@@ -6,6 +6,8 @@ realistic manner. That is, mesages are logged in context of a message with a
 small number of fields.
 """
 
+from __future__ import unicode_literals
+
 import time
 
 from eliot import Message, start_action
diff --git a/docs/source/news.rst b/docs/source/news.rst
index b0409df..d501e84 100644
--- a/docs/source/news.rst
+++ b/docs/source/news.rst
@@ -1,6 +1,13 @@
 What's New
 ==========
 
+1.7.0
+^^^^^
+
+Features:
+
+* Generating messages is much faster.
+
 1.6.0
 ^^^^^
 
diff --git a/eliot/_action.py b/eliot/_action.py
index 9549ca2..09ed9b8 100644
--- a/eliot/_action.py
+++ b/eliot/_action.py
@@ -20,10 +20,9 @@ from pyrsistent import (
     PClass,
     optional,
     pmap_field,
-    pvector_field,
     pvector, )
 from boltons.funcutils import wraps
-from six import text_type as unicode, integer_types, PY3
+from six import text_type as unicode, PY3
 
 from ._message import (
     Message,
@@ -108,7 +107,7 @@ def use_asyncio_context():
     _context.get_sub_context = AsyncioContext().get_stack
 
 
-class TaskLevel(PClass):
+class TaskLevel(object):
     """
     The location of a message within the tree of actions of a task.
 
@@ -117,23 +116,45 @@ class TaskLevel(PClass):
         3]} indicates this is the third message within an action which is
         the second item in the task.
     """
+    def __init__(self, level):
+        self._level = level
 
-    level = pvector_field(integer_types)
+    def as_list(self):
+        """Return the current level.
 
-    # PClass really ought to provide this ordering facility for us:
-    # tobgu/pyrsistent#45.
+        @return: List of integers.
+        """
+        return self._level[:]
+
+    # Backwards compatibility:
+    @property
+    def level(self):
+        return pvector(self._level)
 
     def __lt__(self, other):
-        return self.level < other.level
+        return self._level < other._level
 
     def __le__(self, other):
-        return self.level <= other.level
+        return self._level <= other._level
 
     def __gt__(self, other):
-        return self.level > other.level
+        return self._level > other._level
 
     def __ge__(self, other):
-        return self.level >= other.level
+        return self._level >= other._level
+
+    def __eq__(self, other):
+        if other.__class__ != TaskLevel:
+            return False
+        return self._level == other._level
+
+    def __ne__(self, other):
+        if other.__class__ != TaskLevel:
+            return True
+        return self._level != other._level
+
+    def __hash__(self):
+        return hash(tuple(self._level))
 
     @classmethod
     def fromString(cls, string):
@@ -152,7 +173,7 @@ class TaskLevel(PClass):
 
         @return: L{unicode} representation of the L{TaskLevel}.
         """
-        return "/" + "/".join(map(unicode, self.level))
+        return "/" + "/".join(map(unicode, self._level))
 
     def next_sibling(self):
         """
@@ -161,7 +182,9 @@ class TaskLevel(PClass):
 
         @return: L{TaskLevel} which follows this one.
         """
-        return TaskLevel(level=self.level.set(-1, self.level[-1] + 1))
+        new_level = self._level[:]
+        new_level[-1] += 1
+        return TaskLevel(level=new_level)
 
     def child(self):
         """
@@ -169,7 +192,9 @@ class TaskLevel(PClass):
 
         @return: L{TaskLevel} which is the first child of this one.
         """
-        return TaskLevel(level=self.level.append(1))
+        new_level = self._level[:]
+        new_level.append(1)
+        return TaskLevel(level=new_level)
 
     def parent(self):
         """
@@ -178,9 +203,9 @@ class TaskLevel(PClass):
 
         @return: L{TaskLevel} which is the parent of this one.
         """
-        if not self.level:
+        if not self._level:
             return None
-        return TaskLevel(level=self.level[:-1])
+        return TaskLevel(level=self._level[:-1])
 
     def is_sibling_of(self, task_level):
         """
diff --git a/eliot/_message.py b/eliot/_message.py
index 60c61a5..320daee 100644
--- a/eliot/_message.py
+++ b/eliot/_message.py
@@ -9,7 +9,7 @@ from uuid import uuid4
 
 from six import text_type as unicode
 
-from pyrsistent import PClass, thaw, pmap_field, pmap
+from pyrsistent import PClass, pmap_field
 
 MESSAGE_TYPE_FIELD = 'message_type'
 TASK_UUID_FIELD = 'task_uuid'
@@ -55,7 +55,7 @@ class Message(object):
 
         The keyword arguments will become contents of the L{Message}.
         """
-        _class.new(**fields).write()
+        _class(fields).write()
 
     def __init__(self, contents, serializer=None):
         """
@@ -70,7 +70,7 @@ class Message(object):
             L{eliot.Logger} may choose to serialize the message. If you're
             using L{eliot.MessageType} this will be populated for you.
         """
-        self._contents = pmap(contents)
+        self._contents = contents.copy()
         self._serializer = serializer
 
     def bind(self, **fields):
@@ -78,13 +78,15 @@ class Message(object):
         Return a new L{Message} with this message's contents plus the
         additional given bindings.
         """
-        return Message(self._contents.update(fields), self._serializer)
+        contents = self._contents.copy()
+        contents.update(fields)
+        return Message(contents, self._serializer)
 
     def contents(self):
         """
         Return a copy of L{Message} contents.
         """
-        return dict(self._contents)
+        return self._contents.copy()
 
     def _timestamp(self):
         """
@@ -110,7 +112,7 @@ class Message(object):
             task_level = [1]
         else:
             task_uuid = action._identification[TASK_UUID_FIELD]
-            task_level = thaw(action._nextTaskLevel().level)
+            task_level = action._nextTaskLevel().as_list()
         timestamp = self._timestamp()
         new_values = {
             TIMESTAMP_FIELD: timestamp,
@@ -121,7 +123,8 @@ class Message(object):
             "message_type" not in self._contents
         ):
             new_values["message_type"] = ""
-        return self._contents.update(new_values)
+        new_values.update(self._contents)
+        return new_values
 
     def write(self, logger=None, action=None):
         """
@@ -141,7 +144,7 @@ class Message(object):
         if logger is None:
             logger = _output._DEFAULT_LOGGER
         logged_dict = self._freeze(action=action)
-        logger.write(dict(logged_dict), self._serializer)
+        logger.write(logged_dict, self._serializer)
 
 
 class WrittenMessage(PClass):
diff --git a/eliot/_output.py b/eliot/_output.py
index 087ce05..da83f22 100644
--- a/eliot/_output.py
+++ b/eliot/_output.py
@@ -218,7 +218,7 @@ class Logger(object):
                             self._safeUnicodeDictionary(dictionary)
                         }
                     )
-                    self._destinations.send(dict(msg._freeze()))
+                    self._destinations.send(msg._freeze())
                 except:
                     # Nothing we can do here, raising exception to caller will
                     # break business logic, better to have that continue to
diff --git a/setup.py b/setup.py
index b9b9bba..9f76f6f 100644
--- a/setup.py
+++ b/setup.py
@@ -62,7 +62,8 @@ setup(
             "sphinx",
             "sphinx_rtd_theme",
             "flake8",
-            "yapf"
+            "yapf",
+            "pytest"
         ]
     },
     entry_points={

Hypothesis tests have issues (running too slowly? Flaky?)
E.g. https://travis-ci.org/itamarst/eliot/jobs/490759458
diff --git a/eliot/tests/__init__.py b/eliot/tests/__init__.py
index 0bd69e7..052ada6 100644
--- a/eliot/tests/__init__.py
+++ b/eliot/tests/__init__.py
@@ -1,3 +1,8 @@
 """
 Tests for the eliot package.
 """
+
+# Increase hypothesis deadline so we don't time out on PyPy:
+from hypothesis import settings
+settings.register_profile("eliot", deadline=1000)
+settings.load_profile("eliot")
diff --git a/eliot/tests/test_action.py b/eliot/tests/test_action.py
index dc1113b..500004d 100644
--- a/eliot/tests/test_action.py
+++ b/eliot/tests/test_action.py
@@ -960,6 +960,37 @@ class TaskLevelTests(TestCase):
             )
         )
 
+    def test_equality(self):
+        """
+        L{TaskChild} correctly implements equality and hashing.
+        """
+        a = TaskLevel(level=[1, 2])
+        a2 = TaskLevel(level=[1, 2])
+        b = TaskLevel(level=[2, 999])
+        self.assertTrue(
+            all(
+                [
+                    a == a2,
+                    a2 == a,
+                    a != b,
+                    b != a,
+                    not b == a,
+                    not a == b,
+                    not a == 1,
+                    a != 1,
+                    hash(a) == hash(a2),
+                    hash(b) != hash(a)
+                ]
+            )
+        )
+
+    def test_as_list(self):
+        """
+        L{TaskChild.as_list} returns the level.
+        """
+        self.assertEqual(TaskLevel(level=[1, 2, 3]).as_list(),
+                         [1, 2, 3])
+
     @given(lists(task_level_indexes))
     def test_parent_of_child(self, base_task_level):
         """

pip install -e .[dev]
parse_log_pytest
pytest
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio pytest
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs @ file:///croot/attrs_1668696182826/work
Babel==2.14.0
bleach==6.0.0
boltons==25.0.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
coverage==7.2.7
cryptography==44.0.2
docutils==0.17.1
-e git+https://github.com/itamarst/eliot.git@285e5880e5bea096bf3651077d6d8d3571110887#egg=eliot
exceptiongroup==1.2.2
execnet==2.0.2
flake8==5.0.4
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
hypothesis==6.79.4
idna==3.10
imagesize==1.4.1
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
jaraco.classes==3.2.3
jeepney==0.9.0
Jinja2==3.1.6
keyring==23.9.3
markdown-it-py==2.2.0
MarkupSafe==2.1.5
mccabe==0.7.0
mdurl==0.1.2
more-itertools==9.1.0
packaging @ file:///croot/packaging_1671697413597/work
pkginfo==1.10.0
platformdirs==4.0.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pycparser==2.21
pyflakes==2.5.0
Pygments==2.17.2
pyrsistent==0.19.3
pytest==7.1.2
pytest-asyncio==0.21.2
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-xdist==3.5.0
pytz==2025.2
readme-renderer==37.3
requests==2.31.0
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==13.8.1
SecretStorage==3.3.3
six==1.17.0
snowballstemmer==2.2.0
sortedcontainers==2.4.0
Sphinx==4.3.2
sphinx-rtd-theme==1.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
testtools==2.7.1
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
twine==4.0.2
typing_extensions==4.7.1
urllib3==2.0.7
webencodings==0.5.1
yapf==0.43.0
zipp @ file:///croot/zipp_1672387121353/work
zope.interface==6.4.post2

name: eliot
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - babel==2.14.0
      - bleach==6.0.0
      - boltons==25.0.0
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - coverage==7.2.7
      - cryptography==44.0.2
      - docutils==0.17.1
      - exceptiongroup==1.2.2
      - execnet==2.0.2
      - flake8==5.0.4
      - hypothesis==6.79.4
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.2.0
      - jaraco-classes==3.2.3
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==23.9.3
      - markdown-it-py==2.2.0
      - markupsafe==2.1.5
      - mccabe==0.7.0
      - mdurl==0.1.2
      - more-itertools==9.1.0
      - pkginfo==1.10.0
      - platformdirs==4.0.0
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pyflakes==2.5.0
      - pygments==2.17.2
      - pyrsistent==0.19.3
      - pytest-asyncio==0.21.2
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pytest-xdist==3.5.0
      - pytz==2025.2
      - readme-renderer==37.3
      - requests==2.31.0
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==13.8.1
      - secretstorage==3.3.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sortedcontainers==2.4.0
      - sphinx==4.3.2
      - sphinx-rtd-theme==1.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - testtools==2.7.1
      - twine==4.0.2
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - webencodings==0.5.1
      - yapf==0.43.0
      - zope-interface==6.4.post2
prefix: /opt/conda/envs/eliot

eliot/tests/test_action.py::TaskLevelTests::test_as_list
eliot/tests/test_action.py::ExecutionContextTests::test_globalInstance eliot/tests/test_action.py::ExecutionContextTests::test_multipleCurrent eliot/tests/test_action.py::ExecutionContextTests::test_nothingPushed eliot/tests/test_action.py::ExecutionContextTests::test_popMultiple eliot/tests/test_action.py::ExecutionContextTests::test_popSingle eliot/tests/test_action.py::ExecutionContextTests::test_pushMultiple eliot/tests/test_action.py::ExecutionContextTests::test_pushSingle eliot/tests/test_action.py::ExecutionContextTests::test_threadSafety eliot/tests/test_action.py::ExecutionContextTests::test_threadStart eliot/tests/test_action.py::ActionTests::test_addSuccessFields eliot/tests/test_action.py::ActionTests::test_child eliot/tests/test_action.py::ActionTests::test_childLevel eliot/tests/test_action.py::ActionTests::test_childSerializers eliot/tests/test_action.py::ActionTests::test_failureFinishSerializer eliot/tests/test_action.py::ActionTests::test_finish eliot/tests/test_action.py::ActionTests::test_finishWithBadException eliot/tests/test_action.py::ActionTests::test_multipleFinishCalls eliot/tests/test_action.py::ActionTests::test_nextTaskLevel eliot/tests/test_action.py::ActionTests::test_run eliot/tests/test_action.py::ActionTests::test_runContext eliot/tests/test_action.py::ActionTests::test_runContextUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_runContextUnsetOnReturn eliot/tests/test_action.py::ActionTests::test_start eliot/tests/test_action.py::ActionTests::test_startFieldsNotInFinish eliot/tests/test_action.py::ActionTests::test_startMessageSerialization eliot/tests/test_action.py::ActionTests::test_stringActionCompatibility eliot/tests/test_action.py::ActionTests::test_stringActionCompatibilityWarning eliot/tests/test_action.py::ActionTests::test_successfulFinishSerializer eliot/tests/test_action.py::ActionTests::test_task_uuid eliot/tests/test_action.py::ActionTests::test_withContextNoLogging eliot/tests/test_action.py::ActionTests::test_withContextReturnsaction eliot/tests/test_action.py::ActionTests::test_withContextSetsContext eliot/tests/test_action.py::ActionTests::test_withContextUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_withContextUnsetOnReturn eliot/tests/test_action.py::ActionTests::test_withLogsExceptionMessage eliot/tests/test_action.py::ActionTests::test_withLogsSuccessfulFinishMessage eliot/tests/test_action.py::ActionTests::test_withReturnValue eliot/tests/test_action.py::ActionTests::test_withSetsContext eliot/tests/test_action.py::ActionTests::test_withUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_withUnsetOnReturn eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoLogger eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoParent eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoParentLogStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionSerializers eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionWithParent eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionWithParentLogStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskLogsStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNewAction eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNewUUID eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNoLogger eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskSerializers eliot/tests/test_action.py::StartActionAndTaskTests::test_start_action_default_action_type eliot/tests/test_action.py::StartActionAndTaskTests::test_start_task_default_action_type eliot/tests/test_action.py::PEP8Tests::test_add_success_fields eliot/tests/test_action.py::PEP8Tests::test_continue_task eliot/tests/test_action.py::PEP8Tests::test_serialize_task_id eliot/tests/test_action.py::SerializationTests::test_continueTaskNoLogger eliot/tests/test_action.py::SerializationTests::test_continueTaskRequiredTaskId eliot/tests/test_action.py::SerializationTests::test_continueTaskReturnsAction eliot/tests/test_action.py::SerializationTests::test_continueTaskStartsAction eliot/tests/test_action.py::SerializationTests::test_continueTaskUnicode eliot/tests/test_action.py::SerializationTests::test_serializeTaskId eliot/tests/test_action.py::TaskLevelTests::test_child_greater_than_parent eliot/tests/test_action.py::TaskLevelTests::test_equality eliot/tests/test_action.py::TaskLevelTests::test_fromString eliot/tests/test_action.py::TaskLevelTests::test_from_string eliot/tests/test_action.py::TaskLevelTests::test_next_sibling eliot/tests/test_action.py::TaskLevelTests::test_next_sibling_greater eliot/tests/test_action.py::TaskLevelTests::test_parent_of_child eliot/tests/test_action.py::TaskLevelTests::test_parent_of_root eliot/tests/test_action.py::TaskLevelTests::test_toString eliot/tests/test_action.py::TaskLevelTests::test_to_string eliot/tests/test_action.py::WrittenActionTests::test_action_type_mismatch eliot/tests/test_action.py::WrittenActionTests::test_children eliot/tests/test_action.py::WrittenActionTests::test_different_task_uuid eliot/tests/test_action.py::WrittenActionTests::test_duplicate_task_level eliot/tests/test_action.py::WrittenActionTests::test_end_has_no_status eliot/tests/test_action.py::WrittenActionTests::test_failed_end eliot/tests/test_action.py::WrittenActionTests::test_from_single_child_message eliot/tests/test_action.py::WrittenActionTests::test_from_single_end_message eliot/tests/test_action.py::WrittenActionTests::test_from_single_start_message eliot/tests/test_action.py::WrittenActionTests::test_invalid_start_message_missing_status eliot/tests/test_action.py::WrittenActionTests::test_invalid_start_message_wrong_status eliot/tests/test_action.py::WrittenActionTests::test_invalid_task_level_in_start_message eliot/tests/test_action.py::WrittenActionTests::test_successful_end eliot/tests/test_action.py::WrittenActionTests::test_wrong_task_level eliot/tests/test_action.py::WrittenActionTests::test_wrong_task_uuid eliot/tests/test_action.py::FailedActionExtractionTests::test_environmenterror eliot/tests/test_action.py::FailedActionExtractionTests::test_error_in_extracter eliot/tests/test_action.py::FailedActionExtractionTests::test_matching_class eliot/tests/test_action.py::FailedActionExtractionTests::test_regular_fields eliot/tests/test_action.py::FailedActionExtractionTests::test_subclass_falls_back_to_parent eliot/tests/test_action.py::FailedActionExtractionTests::test_subclass_matches_first eliot/tests/test_action.py::PreserveContextTests::test_callable_only_once eliot/tests/test_action.py::PreserveContextTests::test_no_context eliot/tests/test_action.py::PreserveContextTests::test_with_context_calls_underlying eliot/tests/test_action.py::PreserveContextTests::test_with_context_preserves_context eliot/tests/test_action.py::LogCallTests::test_action_type eliot/tests/test_action.py::LogCallTests::test_default_argument_given eliot/tests/test_action.py::LogCallTests::test_default_argument_missing eliot/tests/test_action.py::LogCallTests::test_exception eliot/tests/test_action.py::LogCallTests::test_methods eliot/tests/test_action.py::LogCallTests::test_no_args_return eliot/tests/test_action.py::LogCallTests::test_no_result eliot/tests/test_action.py::LogCallTests::test_pickleable eliot/tests/test_action.py::LogCallTests::test_star_args_kwargs eliot/tests/test_action.py::LogCallTests::test_whitelist_args eliot/tests/test_action.py::LogCallTests::test_wrong_whitelist_args
eliot/_message.py eliot/_action.py setup.py eliot/_output.py benchmarks/serialization.py docs/source/news.rst
eliot/_message.py eliot/_action.py setup.py eliot/_output.py benchmarks/serialization.py docs/source/news.rst